﻿<?xml version="1.0" encoding="utf-8"?><rss version="2.0" 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" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Erik Heemskerk</title><description>Thoughts and opinions of a software developer.</description><link>https://www.erikheemskerk.nl/</link><generator>GhostSharp</generator><lastBuildDate>Sun, 15 Jun 2025 09:36:08 GMT</lastBuildDate><atom:link href="https://www.erikheemskerk.nl/rss.xml" rel="self" type="application/rss+xml" /><ttl>60</ttl><image><url>https://www.erikheemskerk.nl/favicon.png</url><title>Erik Heemskerk</title><link>https://www.erikheemskerk.nl/</link></image><item><title>Making a Trello clone using htmx</title><description>Is it possible to create a Trello clone using htmx? Yes, and I’ll show you how.</description><link>https://www.erikheemskerk.nl/htmx-trello-clone/</link><guid isPermaLink="false">b80cd574882547469404d8ab2bda3dca</guid><category>htmx</category><category>Hypermedia</category><category>HTML</category><category>CSS</category><category>JavaScript</category><category>hyperscript</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Sat, 21 Dec 2024 14:15:00 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2016-12-13-Achtergrond-3.2.svg.png" alt="Making a Trello clone using htmx" /><p>In <a href="https://www.erikheemskerk.nl/htmx-simplicity/">my last article</a>, I looked at front-end technologies and sang the praises of htmx, which I declared is a simpler way of implementing dynamic web applications. Simpler than the prevailing choice of today, which usually means some form of React or Vue, and which forces you to use JavaScript and have complicated build processes in order to get something working.</p>
<p>Predictably, I got some reactions from front-end developers. Some of those were positive or ‘positive but’. Many of the rest were along a few broad lines of thought, such as:</p>
<ul>
<li>htmx is only suitable for ‘small’ applications, and you’ll need a SPA framework when it becomes too large;</li>
<li>I’ll still need a JSON API for my mobile apps, and I don’t want to do the same work twice;</li>
<li>SPA frameworks are bad, but Next/Nuxt/Sveltekit are basically htmx, but magic;</li>
<li>Your examples suck and a <em>real</em> developer would do this differently in React;</li>
<li>Actually, htmx is just another framework;</li>
<li>This doesn’t look any simpler;</li>
<li>People promoting &lt;not my solution&gt; should learn what they’re talking about before they write something;</li>
<li>It would be so hard/impossible to build X using htmx.</li>
</ul>
<p>Some of these have already been addressed by the essays on the htmx site. Let’s focus on the last one. And let’s not talk about building X, the website formerly known as Twitter. Elon Musk is such a dick for associating the universal placeholder with that cesspool of hatred and censorship. Anyway, moving on. Let’s try and build a Trello clone using htmx. Why Trello? Well, because someone specifically called me out on it. ‘Try making a clone of <a href="https://trellix.fly.dev/">trellix</a>, an app built with remix’. To which I said ‘that doesn’t look too difficult’. So let’s do it.</p>
<h2 id="step-1-what-are-we-building">Step 1: What are we building?</h2>
<p>Its name implies that Trellix is a Trello clone. For those of you don’t know Trello: Trello is a work organizer, basically a very fancy ‘to do’ app, that lets you put cards, representing work, into stacks, which usually represent statuses, like ‘to do’, ‘doing’, ‘blocked’, and ‘done’. These stacks are represented as vertical ‘lanes’, and you can drag and drop cards between lanes or to a different position in the same lane. All of this is done on a ‘board’, which you can think of like a project. Boards are the ‘root’ of the user interface; except for managing boards themselves, everything is done in the context of a board.</p>
<figure>
<p><img src="/content/images/2024/trellix_board.png" alt="A simple board created using Trellix" /></p>
<figcaption>Trellix in all its blendered - sorry - Remixed glory</figcaption>
</figure>
<p>What we’ll be creating is the following behaviors:</p>
<ul>
<li>Creating a new board;</li>
<li>Renaming a board;</li>
<li>Creating lanes;</li>
<li>Creating cards;</li>
<li>Reordering cards in a lane;</li>
<li>Moving cards between lanes;</li>
<li>Removing cards.</li>
</ul>
<p>What about, for example, renaming lanes? Or editing cards? First of all, some of those are not implemented by Trellix, either, but second of all, the point of this is not to create an entire application and solve all of the potentially hard problems, but to demonstrate how simple it can be to implement something that <em>looks</em> very complicated. I’ll also skip the authentication and sign-up part, to keep the scope small.</p>
<h2 id="step-2-how-are-we-building-it">Step 2: How are we building it?</h2>
<p>Obviously, we’re using htmx, but what else? Because htmx is such a back-end-agnostic solution, I could have used pretty much anything. I chose something I’m intimately familiar with, to avoid the entire article being discredited because some troll goes ‘lol, that not how you do that in go’. ASP.NET Core MVC it is. I chose MVC over Razor Pages, because MVC gives you a little bit more control, and lets you, for example, easily return a different view depending on the outcome of an operation.</p>
<p>We’ll use Bootstrap 5 as a CSS ‘framework’, because I’m familiar with it, it’s succinct, and it provides a good starting point.</p>
<h2 id="step-3-lets-build">Step 3: Let’s build!</h2>
<p>I’ll skip over all of the plumbing required to make it work in .NET, but feel free to check out <a href="https://github.com/heemskerkerik/htmello">the source code</a>. Remember that the context of this application is as a htmx proof-of-concept, not as an example of how to build a .NET application. Corners have been cut, and there are undoubtably many edge cases that are not handled.<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a></p>
<p>For the most part, discussing which what CSS features or Bootstrap classes were used is outside of the scope of this article. Where it’s really used to fix a problem, I will discuss it. I’ll also omit most of the classes and such from the code in this post, if they’re only used for styling purposes.</p>
<h2 id="creating-boards">Creating boards</h2>
<p>Our index page will be simple. There will be a form to create new boards, and a list of existing boards which you can navigate to.</p>
<p>Our first interesting thing is to let users create boards, so they actually have a canvas to work on. This will just be a form that will <code>POST</code> to an endpoint, which will create the board and redirect the user to the newly created board. There’s no real advantage in doing a partial update here, so we won’t. The redirect is the old PRG (POST-redirect-GET) pattern in action. Just because we can, we’ll make the form a ‘<a href="https://htmx.org/docs/#boosting">boosted</a>’ form. This means the form will be a regular old HTML form, but when JavaScript is enabled, htmx will turn it into an asynchronous fetch request.</p>
<p>This is what the form looks like:</p>
<pre><code class="language-html">&lt;form method=&quot;post&quot;
      action=&quot;/boards&quot;
      hx-boost=&quot;true&quot;&gt;
    &lt;div&gt;
        &lt;label for=&quot;newBoardName&quot;&gt;Name&lt;/label&gt;
        &lt;input type=&quot;text&quot; 
               required 
               name=&quot;Board.BoardName&quot;
               id=&quot;newBoardName&quot;
               value=&quot;@Model.Board.BoardName&quot; 
               autocomplete=&quot;off&quot; /&gt;
    &lt;/div&gt;
    &lt;div&gt;
        &lt;label for=&quot;newBoardColor&quot;&gt;Color&lt;/label&gt;
        &lt;div&gt;
            &lt;input type=&quot;color&quot; 
                   id=&quot;newBoardColor&quot;
                   name=&quot;Board.Color&quot;
                   value=&quot;@Model.Board.Color&quot; /&gt;
        &lt;/div&gt;
        &lt;button type=&quot;submit&quot;&gt;Create&lt;/button&gt;
    &lt;/div&gt;
&lt;/form&gt;
</code></pre>
<p>If you’re not familiar, the <code>@</code> syntax is used in Razor to indicate a C# expression.</p>
<p>Handling the request is simple enough: create the board, then redirect to it. There’s a problem, though: pressing the back button doesn’t work as you’d expect.</p>
<p>When the request is boosted, using htmx, here’s what happens: htmx will send a <code>POST</code>, which will result in a response with a 302 status (a redirect). Htmx will follow the redirect, request the indicated page and render the response just fine. A boosted request tries to behave like a ‘normal’ request in many ways, including history support, or in other words: you can use the back button to navigate to the previous state. To do this, htmx takes a snapshot of the page’s DOM just before swapping in the respone, and caches it in local storage. When you press the back button, htmx simply swaps in the cached response; there’s no request to refresh any of the data. This makes sense if you consider that whatever state the page was in could have been the result of an untold number of other interactions.</p>
<p>In contrast, when the form is submitted without htmx, the <code>POST</code> request results in a 302 status (a redirect), which prompts a second request. Because the response to the first is a redirect, the first response is not added to the browser’s history. When you click the back arrow on the newly created board page, the browser will just issue a new request to the index page, which will display the board you just added.</p>
<p>There is a number of ways we can handle this. We can turn the redirect to the board page into a full page load with the <a href="https://htmx.org/headers/hx-redirect/"><code>HX-Redirect</code> header</a>. This works, but it’s essentially defeating the point of having a boosted request.</p>
<p>We can prevent htmx from caching the index page, by using <a href="https://htmx.org/attributes/hx-history/"><code>hx-history=&quot;false&quot;</code></a>. This also works, but it can be a heavy-handed solution, since the <em>entire page</em> is requested when you navigate back to it.</p>
<p>We can also use the <a href="https://htmx.org/attributes/hx-trigger/"><code>load</code> trigger</a> to request only the list of boards whenever the page loads. This might be the best solution if you want to avoid loading the entire page because it’s slow to load, for example. By default, htmx has no separate event for ‘a page was restored from the history cache’ (there’s <a href="https://github.com/bigskysoftware/htmx-extensions/blob/main/src/restored/README.md">an extension</a> that adds it, though). This means that each time you navigate to the page, whether it’s ‘normally’ or backwards in the browser history, the <code>load</code> event will always be triggered. In other words: you click your way to the index page, which comes with a fresh list of boards, and then as soon as the page is done loading, a request is triggered to get a <em>fresher</em> list of boards. A way to deal with this somewhat gracefully would be to provide the request with some version information that lets the endpoint respond with a 204 (No Content) response in case there is nothing more recent. Still, that is an additional request.</p>
<p>Since our index page is pretty light, let’s use the simplest solution and set <code>hx-history=&quot;false&quot;</code> on the list of boards.</p>
<h2 id="renaming-a-board">Renaming a board</h2>
<p>The first thing on our board page is the board title, which you should be able to change. Well, the first thing is really the background color of the board. The easiest way to implement this would be to set a <code>style=&quot;background-color: &lt;something&gt;&quot;</code> on the body of the page, since that covers the entire page from top to bottom. Unfortunately, htmx does not touch the <code>body</code> element when doing partial page loads. Fortunately, we live in a day and age where it is trivial to make a lay-out that covers the entire page.</p>
<p>Our page set-up is like most Bootstrap pages: we have a navigation bar at the top, and a ‘container’ below it, which contains the page content. We set the <code>body</code> to <code>display: flex</code> to enable the ease of use of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox">CSS Flexbox</a>, and then we set <code>min-height: 100vh</code> (the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/length#vh"><code>vh</code> unit</a> represents 1% of the viewport’s height) to force it to cover all of the browser window. Then we set <code>flex-grow: 1</code> on our container element and we’re set! Modern CSS is just amazing.</p>
<p>Back to renaming a board. In Trellix, the process is made very sleek; when you click the board name, it’s replaced by a borderless text field with the name, and you can immediately start typing and press Enter to commit your changes, or tab away or click outside the field to dismiss your changes. Let’s see if we can do something similar.</p>
<p>The first thing to solve is to show an input field when we click the title. That’s easy enough; we just expose an endpoint that returns the input field, plus whatever other markup is required, and then we wire up the board title to request it when clicked. Since ‘click’ is the default trigger for anything that’s not an input or form, this is very concise:</p>
<pre><code class="language-html">&lt;h1 hx-get=&quot;/boards/@Model.BoardId/editName&quot;
    hx-swap=&quot;outerHTML&quot;&gt;
  @Model.Name
&lt;/h1&gt;
</code></pre>
<p>The form to do the rename itself, which would be returned by the <code>editName</code> endpoint, is also not that special. I’m using a form here because we don’t want the request to be triggered on every key press, but instead only when the user presses Enter. We <em>could</em> use <code>hx-trigger</code> for this, but using a form is easier, cleaner, and it might eventually be necessary if you want to include CSRF tokens.</p>
<pre><code class="language-html">&lt;form hx-put=&quot;/boards/@Model.BoardId/editName&quot; 
      hx-swap=&quot;outerHTML&quot;&gt;
  &lt;input type=&quot;text&quot; 
         name=&quot;boardName&quot; 
         value=&quot;@Model.Name&quot; 
         required 
         placeholder=&quot;Enter board name&quot; 
         autocomplete=&quot;off&quot;
         autofocus /&gt;
&lt;/form&gt;
</code></pre>
<p>Note the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus"><code>autofocus</code> attribute</a> on the text field. It has been supported by browsers since 2011 to specify that a particular input should receive focus when the page is loaded. Htmx supports this attribute as well, so you can use it to move the focus after loading in new content, instead of having to manually write calls to <code>focus()</code>.</p>
<p>The response from the endpoint handling the form should be an updated <code>h1</code> tag, which can then be replaced by a form when it’s clicked, and so on.</p>
<h3 id="not-renaming-a-board">Not renaming a board</h3>
<p>Letting users rename a board is not that difficult, but what if they change their minds, or if they clicked the title by accident? Canceling the editing process should be as easy as clicking somewhere outside the input field, or pressing Tab to focus on something else.<a id="fnref:2" href="#fn:2" class="footnote-ref"><sup>2</sup></a> At first glance, this looks like something you definitely need JavaScript for, but we actually don’t. Combining JavaScript events with the various htmx attribute will do the job.</p>
<p>When you press Tab, the focus moves to the next element, firing an event on the original element which JavaScript calls <code>blur</code>. Get it? Because it’s the opposite of <code>focus</code>? JavaScript has some weird event names. Anyway, the <code>blur</code> event is fired if, for whatever reason, the input loses focus, and that includes clicking outside it.</p>
<pre><code class="language-html">&lt;div hx-get=&quot;/boards/@Model.BoardId/name&quot;
     hx-trigger=&quot;blur from:#boardName&quot;
     hx-swap=&quot;outerHTML&quot;&gt;
  &lt;form hx-put=&quot;/boards/@Model.BoardId/editName&quot; 
        hx-swap=&quot;outerHTML&quot;
        hx-target=&quot;closest div&quot;&gt;
      &lt;input type=&quot;text&quot;
             id=&quot;boardName&quot;
             name=&quot;boardName&quot;
             value=&quot;@Model.Name&quot;
             required
             placeholder=&quot;Enter board name&quot;
             autocomplete=&quot;off&quot;
             autofocus /&gt;
  &lt;/form&gt;
&lt;/div&gt;
</code></pre>
<p>The trigger <code>blur from:#boardName</code> indicates the request should be triggered whenever a <code>blur</code> event is fired by the element with ID <code>boardName</code>. Alternatively, we could have made the text field itself make the request and replace the form, but it seemed neater to have a container element for this purpose. To be honest, that is how I originally implemented it, but while editing this article, I changed my mind. It happens; you write some code and you’re happy with it, then a month later you hate it.</p>
<p>Note that because we now have a container element around the form, we need to make sure we replace it as well when we submit the form. We do that by specifying <code>hx-target=&quot;closest div&quot;</code>, or by giving the container element an ID and using that as the target.<a id="fnref:3" href="#fn:3" class="footnote-ref"><sup>3</sup></a> I find <a href="https://htmx.org/attributes/hx-target/"><code>closest</code></a> to be a bit more readable, but in this case that’s a matter of taste.</p>
<p>This completes the implementation of the ‘click-to-edit’ pattern seen on the htmx site. Click an element to replace it with an editable version and a ‘cancel’ button, which restores it to the static version, as does clicking the optional ‘save’ button or pressing Enter.</p>
<h3 id="changing-the-page-title">Changing the page title</h3>
<p>Since a board is the primary focus of the board page, you would expect it to reflect the board’s name in the page title. It would be nice if the title would update when we change its name. With htmx, this is as easy as including a <code>&lt;title&gt;</code> tag in the response.</p>
<p>In response to changing the title, we’ll just add it in the response, next to the new board title.</p>
<pre><code class="language-aspnet">@if ((bool) (ViewData[&quot;RenderTitleTag&quot;] ?? false))
{
  &lt;title&gt;@Model.Name - Htmello&lt;/title&gt;
}
&lt;h1 hx-get=&quot;/boards/@Model.BoardId/editName&quot;
    hx-swap=&quot;outerHTML&quot;&gt;
  @Model.Name
&lt;/h1&gt;
</code></pre>
<p><code>ViewData</code> is ASP.NET’s ad hoc view data dictionary, which is a convenient (if a bit ugly) way to pass optional data from controllers to views. This is the same partial view used to render the board name as part of a full page request, so the title tag is rendered conditionally.</p>
<p>And yes, ‘Htmello’ is the name I came up with for my htmx-based Trello clone.</p>
<h2 id="creating-lanes">Creating lanes</h2>
<p>Now that we have a title, we need the ‘lanes’ that hold cards. The interactions around creating lanes are a little bit more complicated.</p>
<ul>
<li>The ‘add lane’ button always needs to be to the right of the last lane;</li>
<li>Clicking the ‘add lane’ button needs to reveal a text field and move focus to it, and moving focus away from that needs to hide the text field and show the ‘add lane’ button again;</li>
<li>Pressing Enter and finishing the interaction needs to result in a lane being added to the right of the last lane, and the ‘add lane’ button re-appearing next to the new lane;</li>
<li>Just to make things a bit more interesting: lane names need to be unique, so a submission can also be rejected with a validation error.</li>
</ul>
<p>Could this have been solved purely using htmx? Probably. Would it have been the most practical? Probably not. So, what then? Obviously, I could have used plain JavaScript to wire up some of the more subtle interactions, such as resetting the add lane button when focusing away. That would still have required a fair bit of boilerplate code to wire up the code in the first place, and to do things like DOM traversal and manipulation. Instead, I’ll use a language that is better suited for these kinds of small interactions: hyperscript, from the same guy who created htmx. In fact, it is advertised as the ‘companion of htmx’.</p>
<h3 id="sidebar-hyperscript">&lt;sidebar&gt;: hyperscript</h3>
<p><a href="https://hyperscript.org/">hyperscript</a> is meant to be a hypermedia-friendly scripting language. This means, among others, that the language is designed to be used in-line (i.e. in an HTML attribute). JavaScript can be used in-line, of course, but it’s cumbersome and relatively verbose. Doing something simple like showing a single element would be trivial, but doing multiple things quickly starts to become hard to read. In-line JavaScript handlers also only handle a single type of event on a single element; if you need to handle an event on multiple elements, you’ll need to duplicate the same <code>onclick</code> attribute (or whatever attribute you need) for every element. For this reason, it’s recommended to write JavaScript separately from your HTML and write long, verbose statements describing what you want. That, or use jQuery.</p>
<pre><code class="language-js">document.addEventListener(&quot;DOMContentLoaded&quot;, function() {
  var button = document.querySelector(&quot;.add-lane-button&quot;);
  button.addEventListener(&quot;click&quot;, function() {
    var form = document.querySelector(&quot;.add-lane-form&quot;);
    form.style.display = &quot;block&quot;;
    this.style.display = &quot;none&quot;;
  });
});
</code></pre>
<p>Now contrast this with hyperscript, which has event handling and DOM manipulation built into the language.</p>
<pre><code class="language-html">&lt;button _=&quot;on click 
             show the first .add-lane-form
             hide me&quot;&gt;
  Add lane
&lt;/button&gt;
</code></pre>
<p><code>_=</code> is hyperscript for ‘apply the following behaviors to this element’. The behavior is simple: when the button is clicked, show the first element with the class <code>add-lane-form</code>, and then hide the button.</p>
<p>Obviously, this is a <em>very</em> simple example and a bit contrived, but it does work, and it showcases a philosophy behind both hyperscript and htmx: locality of behavior. It is much easier to understand what behaviors actually apply to a particular element when that behavior is defined in its close vicinity.</p>
<h3 id="sidebar">&lt;/sidebar&gt;</h3>
<p>So, back to our main event. Let’s first tackle clicking the ‘add lane’ button, which we already partially solved in the sidebar. In hyperscript, this almost looks like pseudo-code.</p>
<pre><code class="language-html">&lt;button _=&quot;on click 
             show next .add-lane-form 
             hide me 
             focus() on the first &lt;input[type=text] /&gt; in the next .add-lane-form&quot;&gt;
  Add lane
&lt;/button&gt;
&lt;div class=&quot;add-lane-form&quot; style=&quot;display:none&quot;&gt;
  &lt;!-- form goes here --&gt;
  &lt;input type=&quot;text&quot; /&gt;
&lt;/div&gt;
</code></pre>
<p>So: we have a button with some hyperscript and an invisible <code>div</code> with a text field inside it. The hyperscript looks similar to that of the sidebar. When the button is clicked, show the ‘next’ element with the class <code>add-lane-form</code>, then hide the button, and then something that looks complicated.</p>
<p>Let’s first address the ‘next’ bit. Like htmx, hyperscript supports relative queries, i.e. finding elements relative to the current element. <a href="https://hyperscript.org/expressions/relative-positional/"><code>next</code></a> will find the next element in the DOM, which includes searching through parents and ancestors. In this case, the element is the button’s sibling, but it could be somewhere else entirely.</p>
<p>Then the complicated bit. We can break it down into parts by putting parentheses around those parts.</p>
<pre><code class="language-hyperscript">focus() on
  (the first &lt;input[type=text] /&gt; in
    (the next .add-lane-form))
</code></pre>
<p>Now it looks like more like a maths equation, and it’s solved in a similar manner; find the next element with class <code>add-lane-form</code>, and within that find the first <code>input</code> that has <code>type=&quot;text&quot;</code>, then call <code>focus()</code> on it.</p>
<p>So, now that we have a visible text field, let’s deal with pressing Enter and actually creating the lane. This is handled by htmx using the <code>hx-post</code> attribute. Let’s zoom out a bit and show the button, the form, and its container.</p>
<pre><code class="language-html">&lt;div class=&quot;add-lane&quot;&gt;
  &lt;button _=&quot;-- omitted for brevity&quot;&gt;
    Add lane
  &lt;/button&gt;
  &lt;div class=&quot;add-lane-form&quot; style=&quot;display:none&quot;&gt;
    &lt;form hx-post=&quot;/boards/@Model.BoardId/lanes&quot;
          hx-target=&quot;.add-lane&quot;
          hx-swap=&quot;beforebegin&quot;&gt;
      &lt;input name=&quot;LaneName&quot;
             type=&quot;text&quot;
             required
             placeholder=&quot;Add a lane&quot;/&gt;
    &lt;/form&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>The <code>form</code> is the tag doing the heavy lifting here. It posts to a particular endpoint, and its target is the outermost <code>div</code> in this snippet, the one with the class <code>add-lane</code>, and its swap type is set to <code>beforebegin</code>. To visualize why this works, let’s look at a picture that visualizes the HTML structure.</p>
<figure>
<p><img src="/content/images/2024/htmello_add_lane_structure.png" alt="Some lanes on a board with lay-out elements highlighted." /></p>
<figcaption>Witness my box drawing prowess.</figcaption>
</figure>
<p>Because of the magic of CSS Flex Box, just adding a <code>div</code> next to the others is enough for everything to be styled correctly. Because the <code>&lt;div class=&quot;add-lane&quot;&gt;</code> is always at the end of the ‘strip’, <code>beforebegin</code> will correctly add lanes after the existing ones, and move the ‘add lane’ control to the right.</p>
<p>The endpoint that adds the new lane only returns the HTML for the newly added lane. At this point, we’re half-way there. After you’ve added a lane, the page now looks like this:</p>
<figure>
<p><img src="/content/images/2024/new_lane_input_visible.png" alt="Some lanes on a board, but a new lane has been added. The input field is still visible and still has a value." /></p>
<figcaption>That text field has outstayed its welcome.</figcaption>
</figure>
<h3 id="validation">Validation</h3>
<p>The text field we used to add the lane is still there, with its value. That’s okay for now; let’s focus on the ‘unhappy flow’ for adding a lane: the name is not unique, so we need to show an error message. How we find that the name is not unique is not that interesting here, but how we get the error message to show up is. If we simply return the error message to the client, it will be added to the DOM where the new lane would show up.</p>
<p>We need to change the effective target element, but that won’t be enough, since the swap method is <code>beforebegin</code>. We’ll also need to change the swap method. Fortunately, htmx supports this through some HTTP response headers. To keep things simple, we’ll just return the entire form with an error message, and we’ll change the target to be <code>this</code> (meaning the element that initiated the request, i.e. the form) and set the swap method to <code>outerHTML</code>, i.e. replace the entire element. If we send the HTTP response like the following, htmx will do what we want.</p>
<pre><code class="language-http">HTTP 200 OK
Content-Type: text/html
HX-Retarget: this
HX-Reswap: outerHTML

&lt;form hx-post=&quot;/boards/1/lanes&quot;
      hx-swap=&quot;beforebegin&quot;
      hx-target=&quot;.add-lane&quot;&gt;
  &lt;input name=&quot;LaneName&quot; value=&quot;Procrastinate&quot; /&gt;
  &lt;!-- and so on --&gt;
  &lt;span class=&quot;text-danger&quot;&gt;Lane names must be unique.&lt;/span&gt;
&lt;/form&gt;
</code></pre>
<p>The <code>HX-Retarget</code> and <code>HX-Reswap</code> <a href="https://htmx.org/reference/#response_headers">headers</a> tell htmx to ignore the target and swap method originally set on the element making the request, and use the new ones instead. This is a nice mechanism for different flows on your page, which could be used for validation, but also for authentication, for example.</p>
<h3 id="hiding-the-field">Hiding the field</h3>
<p>Now that we have validation in place, let’s circle back to the previous problem: the text field is still visible, while we want it to disappear and the ‘add lane’ button to reappear. There are several ways we can tackle this problem.</p>
<p>Instead of adding only the newly added lane, we could replace the entire strip of lanes, including the ‘add lane’ button. That’s not very efficient, and it does not work for another requirement, namely hiding the text field and showing the button when we move focus out of the text field. Let’s call this plan C.</p>
<p>We could trigger a request after we’ve added a lane to replace the ‘add lane’ form. This might be a simple solution, but it also introduces a visible ‘flicker’ between the moment the lane is visually added and the text field is replaced with a hidden one. This happens even with blindingly fast back-ends on a local machine, so imagine how distracting it would be on a spotty 3G connection.</p>
<p>We could use <a href="https://htmx.org/attributes/hx-swap-oob/">out-of-band swapping</a> to add the new lane and replace the form in a single action, but this is not super clean, because it requires a switch in the back-end logic to add the <code>hx-swap-oob</code> attribute or not, depending on the situation.</p>
<p>Perhaps the best solution is to use hyperscript to handle these events. Responding to the user moving the focus away is easy enough; we just hide the form, show the button, and then we reset the text field’s value to an empty string and we hide any validation errors.</p>
<pre><code class="language-html">&lt;input name=&quot;LaneName&quot;
       type=&quot;text&quot;
       required
       placeholder=&quot;Add a lane&quot;
       _=&quot;on blur
            hide &lt;div.add-lane-form /&gt;
            show &lt;button.add-lane-button/&gt;
            set my value to ''
            hide the next .text-danger&quot;/&gt;
</code></pre>
<p>This is pretty straight-forward. However, doing this when a lane has been added seems less obvious. What kind of event can we handle that would indicate this? There’s the <a href="https://htmx.org/events/#htmx:afterOnLoad"><code>htmx:afterOnLoad</code> event</a> from htmx, but that will fire for any successfully completed request, including one where the response shows a validation error. There’s the <a href="https://htmx.org/events/#htmx:afterSettle"><code>htmx:afterSettle</code> event</a>, which fires on the <code>&lt;div class=&quot;add-lane&quot;&gt;</code> when a lane has been added. This is fired <em>very</em> late in the process, though, and that leads to flickering, the thing we were trying to avoid. In addition, it’s pretty brittle; if we change how we add lanes, we could also end up needing to change this.</p>
<p>The proper solution is having the server trigger a custom event whenever a lane has been added. This is done using response headers: just put the name of an event in the <a href="https://htmx.org/headers/hx-trigger/"><code>HX-Trigger</code> header</a>, and htmx will trigger that event when the request completes successfully.</p>
<pre><code class="language-http">HTTP 200 OK
Content-Type: text/html
HX-Trigger: laneAdded

&lt;!-- HTML goes here --&gt;
</code></pre>
<p>These ‘custom events’ are proper JavaScript events, so they can be used to trigger htmx requests, or hyperscript blocks, or anything else that responds to JavaScript events. In our case, we would just add it to the existing <code>on</code> clause, changing it from <code>on blur</code> to <code>on blur or laneAdded from body</code>. The <code>from</code> qualification is needed because htmx triggers custom events on the <code>body</code> element.</p>
<pre><code class="language-html">&lt;input name=&quot;LaneName&quot;
       type=&quot;text&quot;
       required
       placeholder=&quot;Add a lane&quot;
       _=&quot;on blur or laneAdded from body
            hide &lt;div.add-lane-form /&gt;
            show &lt;button.add-lane-button/&gt;
            set my value to ''
            hide the next .text-danger&quot;/&gt;
</code></pre>
<p>And that’s it, that is all of the behavior described at the start of this section covered: toggling a button into a text field that reverts when you cancel the operation or when you complete it successfully. For a complete view, this is all of the HTML that makes this happen:</p>
<pre><code class="language-html">&lt;div class=&quot;add-lane&quot;&gt;
  &lt;button class=&quot;btn add-lane-button&quot;
          _=&quot;on click 
               show next .add-lane-form 
               hide me 
               focus() on the first &lt;input[type=text] /&gt; in the next .add-lane-form&quot;&gt;
    Add lane
  &lt;/button&gt;
  &lt;div class=&quot;add-lane-form&quot;
       style=&quot;display: none&quot;&gt;
    &lt;form hx-post=&quot;/boards/@Model.BoardId/lanes&quot;
          hx-swap=&quot;beforebegin&quot;
          hx-target=&quot;.add-lane&quot;&gt;
      &lt;input name=&quot;LaneName&quot;
             type=&quot;text&quot;
             required
             placeholder=&quot;Add a lane&quot;
             _=&quot;on blur or laneAdded from body
                  hide &lt;div.add-lane-form/&gt;
                  show &lt;button.add-lane-button/&gt;
                  set my value to ''
                  hide the next .text-danger&quot;/&gt;
    &lt;/form&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Could we have used this for the editing of the board name as well? Yes, we very well could have, and that is what is in the final version of the code (as of this writing). It replaces 6 lines of htmx attributes with 8 lines of hyperscript, merges two files together, and removes two entire endpoints. Originally, I implemented it as described earlier, and as I was writing this part of the article I thought of exploring other places where I don’t <em>really</em> need a request to just change what elements are visible.</p>
<h2 id="creating-cards">Creating cards</h2>
<p>Now that we have lanes, creating cards should be a walk in the park. The structure of the UI for creating a card is almost identical to that for creating a lane; there’s a button that turns into a field, which disappears and resets when you actually create a card or dismiss the action. The htmx part is set up in very similar way, although it has a minuscule difference, which has to do with reordering cards, the subject of the next section.</p>
<pre><code class="language-html">&lt;button _=&quot;on click
             show next .add-card-form 
             hide me 
             focus() the first &lt;input[type=text] /&gt; in the next .add-card-form&quot;&gt;
    Add a card
&lt;/button&gt;
&lt;div class=&quot;add-card-form&quot;
     style=&quot;display:none&quot;&gt;
    &lt;form hx-post=&quot;/boards/@Model.BoardId/lanes/@Model.LaneId&quot;
          hx-target=&quot;previous .card-stack&quot;
          hx-swap=&quot;beforeend&quot;&gt;
        &lt;input type=&quot;text&quot;
               name=&quot;cardName&quot; 
               required 
               placeholder=&quot;Add new card&quot; 
               autocomplete=&quot;off&quot;
               _=&quot;on blur or cardAdded from body
                    hide the closest .add-card-form
                    set my value to '' 
                    show &lt;button.add-card-button/&gt;&quot;/&gt;
    &lt;/form&gt;
&lt;/div&gt;
</code></pre>
<h2 id="reordering-cards-in-a-lane">Reordering cards in a lane</h2>
<p>Reordering items in a list, because that is what cards in a lane essentially are, is not as hard as it sounds. We can just add up and down buttons on each item that move them around within a lane. For a better experience, we can let users select an item, and move them around with centrally located up and down buttons. Except <em>of course</em> that’s not what we’re going to do. Trello (and by extension Trellix, and most modern work organization tools) lets you move around cards by clicking and dragging. How are we going to do this in our htmx set-up? Events!</p>
<p>We’ve used events in our approach to resetting the UI when a lane or card has been added, but then we only triggered events using the <code>HX-Trigger</code> header, which then triggered blocks of hyperscript. Now we’re going to send requests, triggered by JavaScript events.</p>
<p>We’ll use the <a href="https://sortablejs.github.io/Sortable/">SortableJS</a> library to deal with the heavy lifting of dragging and dropping. It’s a simple, but capable library, and most importantly, it triggers a JavaScript event whenever a card has been moved to a different position.</p>
<p>SortableJS requires a tiny bit of JavaScript to set up, and a container element in the DOM with some child elements to reorder. In our case, each lane has its own sortable instance, the elements to reorder are the lane’s cards, and the container is a form that will be submitted whenever a card has been moved. Why a form? Because the way we encode the new sort order is by sending the IDs in a particular order.</p>
<p>Let’s say we have three cards with IDs A, B, and C. We’ll give each card a hidden input field with its ID and a common name. When you submit a form, the fields are added in <a href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set">tree order</a>, meaning the order in which they appear in the DOM. In other words, if we add a hidden field called <code>cards</code> to each card, with its ID as the value, and we submit the form containing everything, the form data submitted will be this:</p>
<pre><code class="language-text">cards=A&amp;cards=B&amp;cards=C
</code></pre>
<p>When you reorder elements using SortableJS, they are simply repositioned in the DOM, which means the order in which the IDs appear in the form data will reflect the new order. SortableJS has many different events for different parts of the drag-and-drop cycle, but the <code>sort</code> event is triggered for ‘any change to the list (add / update / remove)’, which make it an ideal candidate, for now.</p>
<p>To put this into practice, let’s first configure the SortableJS library. After adding a script tag to the document, we can do this:</p>
<pre><code class="language-js">htmx.onLoad(function(content) {
    const sortables = content.querySelectorAll(&quot;.card-stack&quot;);
    sortables.forEach(sortable =&gt; {
        Sortable.create(sortable);
    });
});
</code></pre>
<p>In my example repository, there’s a few more lines of code to enable animations and to prevent sorting whenever there’s a request in-flight, but this is definitely functional.</p>
<p>Now we just need to add the cards and the form.</p>
<pre><code class="language-html">&lt;form class=&quot;card-stack&quot;
      hx-put=&quot;/boards/@Model.BoardId/lanes/@Model.LaneId/sortItems&quot;
      hx-trigger=&quot;sort&quot;&gt;
  @foreach (var card in Model.Cards)
  {
    &lt;div class=&quot;lane-card&quot;&gt;
      &lt;input type=&quot;hidden&quot; name=&quot;cards&quot; value=&quot;@card.CardId&quot; /&gt;
      &lt;div class=&quot;card-text&quot;&gt;@card.Title&lt;/div&gt;
    &lt;/div&gt;
  }
&lt;/form&gt;
</code></pre>
<p>Now we just need to implement an endpoint that takes the card IDs in the new order, and uses that information to sort the cards in the application’s persistent storage. In this case, we don’t need a response, because nothing would change. If you would need to deal with concurrent updates, you might need to replace the items in the DOM after sorting them.</p>
<h2 id="moving-cards-between-lanes">Moving cards between lanes</h2>
<p>In the previous section we handled reordering items <em>within a lane</em>. But one of the USPs of Trello is being able to drag items to a different lane to indicate its progress. Obviously, we need to be able to do this. Fortunately, SortableJS supports this scenario, which they call ‘groups’. If we specify the same group name when setting up each sortable instance, we can freely move items between them.</p>
<pre><code class="language-js">htmx.onLoad(function(content) {
    const sortables = content.querySelectorAll(&quot;.card-stack&quot;);
    sortables.forEach(sortable =&gt; {
        Sortable.create(sortable, { group: &quot;cards&quot; });
    });
});
</code></pre>
<p>The only problem is that, because our htmx trigger is the <code>sort</code> event, moving a card from one lane to the other will trigger two requests; one for the lane the card is added to, and one for the lane the card is removed from. The second request will achieve nothing, so we’d rather not have it. We can use more specific events instead:</p>
<ul>
<li><code>add</code> for when an items is added to a lane (from another one);</li>
<li><code>update</code> for when an items order within the lane is changed.</li>
</ul>
<p>Using <code>hx-trigger=&quot;add,update&quot;</code> will do what we want. Our endpoint implementation will need to be a little bit smarter, because the IDs it gets could be from any lane, not just the one that’s being sorted.</p>
<p>And voila, we can move cards every way we want, and it only took a few lines of code.</p>
<h2 id="removing-cards">Removing cards</h2>
<p>While removing cards sounds trivial, I thought it’s interesting to include because it uses some of the more interesting features of htmx to show how little you need for useful interactions.</p>
<p>One way of implementing this is to trigger a refresh of the entire lane. That would be wasteful, though, because in the end we just want to remove a single element from the DOM. The way to go is to use <code>hx-swap=&quot;delete&quot;</code>.</p>
<pre><code class="language-html">&lt;a hx-delete=&quot;/boards/@Model.BoardId/cards/@Model.CardId&quot;
   hx-target=&quot;closest .lane-card&quot;
   hx-swap=&quot;delete&quot;
   hx-params=&quot;none&quot;&gt;
  Delete
&lt;/a&gt;
</code></pre>
<p>When the <code>DELETE</code> request completes successfully (and returns a 2xx-class response), the target element (in this case: the entire card) is removed from the DOM. <code>hx-params</code> is there because the cards are all contained within a form, and by default, htmx will then send all of the form’s fields with the request. While it’s not a problem in this case, it’s better to send a correct request, and specifying <code>hx-params=&quot;none&quot;</code> will do just that.</p>
<h2 id="card-counting">Card counting</h2>
<p>This feature is not a part of Trellix, but even more interesting than removing cards, because it showcases updating multiple parts of the page. We’ll add a badges to indicate how many cards are in each lane, and on the board in total, and to do this we’ll be using several mechanisms.</p>
<figure>
<p><img src="/content/images/2024/board_with_counters.png" alt="A board with several lanes, each have one or more cards. Each lane has a badge showing the number of cards in the lane. The board title has a badge showing the total number of cards on the board." /></p>
<figcaption>A board with counters shown</figcaption>
</figure>
<p>A board with several lanes, each have one or more cards. Each lane has a badge showing the number of cards in the lane. The board title has a badge showing the total number of cards on the board.</p>
<p>To show the overall total number of cards on the page, we’ll use out-of-band swaps, since there is only a single element to update (and so we can show what that looks like). There are only two scenarios that affect the overall total: creating a card and removing a card.</p>
<p>Next to the name of the board, we’ll add a badge with an ID.</p>
<pre><code class="language-html">&lt;span class=&quot;badge bg-primary&quot;
      id=&quot;boardCardCount&quot;&gt;
  @Model.CardCount
&lt;/span&gt;
</code></pre>
<p>In my example repository, this is implemented using a <a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-8.0">view component</a>, which is kind of a ‘lightweight controller’; while it is a component that’s invoked by the view, it has its own ‘controller’ that can execute code and that can be tested independently from its view. This means I don’t need to query the new total number of cards in my controller action that creates a new card, nor do I need to do this in a view.</p>
<p>When a card has been created, I add this badge element next to the newly created card, but with a <code>hx-swap-oob=&quot;true&quot;</code> attribute. The value of the <a href="https://htmx.org/attributes/hx-swap-oob/"><code>hx-swap-oob</code></a> attribute can be <code>true</code> or an <code>hx-swap</code> value, optionally with a CSS selector. If set to <code>true</code>, htmx will look up the target element by its ID and replace its contents.</p>
<p>When a card is removed, the response consists only of this badge, with <code>hx-swap-oob=&quot;true&quot;</code>.</p>
<h3 id="lane-card-counts">Lane card counts</h3>
<p>In addition to creating and removing cards, reordering or moving cards can affect the number of cards in each lane. Each lane will have its own count, so in this case, we’ll have each element update itself in response to certain events.</p>
<p>We could trigger the <code>cardAdded</code> event when we create a card, but that would cause each lane’s card count element to refresh itself, which is a bit of a waste. Instead, we can trigger an event with the lane ID added (i.e. <code>cardAdded:12345</code>), and only have each element respond to events within its own lane.</p>
<pre><code class="language-html">&lt;span class=&quot;badge bg-primary&quot;
      hx-get=&quot;/boards/@Model.BoardId/lanes/@Model.LaneId/cardCount&quot;
      hx-trigger=&quot;cardAdded:@Model.LaneId from:body&quot;
      hx-swap=&quot;innerHTML&quot;&gt;
  @Model.Cards.Count
&lt;/span&gt;
</code></pre>
<p>We can do similar things when a card is removed and when cards have been sorted. The last case is slightly more complicated from an implementation point of view, since we’ll need to keep track of which lanes we affect when we sort items.</p>
<p>Note how the swap method is <code>innerHTML</code> (which it is by default, but I added it to be explicit). This means the response to refreshing the card count is literally just a number with the <code>text/html</code> content type, and that’s enough.</p>
<h2 id="before-we-leave-alpine.js">Before we leave: Alpine.js</h2>
<p>A coworker who I asked to review this article said he found hyperscript’s syntax to be confusing and that he would prefer something a bit more ‘like JavaScript’. I briefly investigated what it would look like to use <a href="https://alpinejs.dev">Alpine.js</a> instead of hyperscript.</p>
<p>Like hyperscript, Alpine.js is very hypermedia-friendly, and can be ‘installed’ by adding a single script file to the page. <em>Unlike</em> hyperscript, it styles itself as a JavaScript framework, rather than a language.</p>
<p>Where hyperscript is event-driven but procedural, Alpine.js is ‘reactive’, which means there is a lot of magic happening, and when it doesn’t magic, figuring out what’s wrong could be a tall order.</p>
<p>That said, let’s look at what editing the title would look like using Alpine.js. First, as a reminder, let’s look at the hyperscript version. I’ve trimmed it down a bit for brevity’s sake.</p>
<pre><code class="language-html">&lt;h1 _=&quot;on click
         hide me
         show the next &lt;form /&gt;
         focus() on the first &lt;input[type=text]/&gt; in the next &lt;form /&gt;&quot;&gt;
  Title
&lt;/h1&gt;
&lt;form style=&quot;display: none&quot;&gt;
  &lt;input value=&quot;Title&quot;
         _=&quot;on blur
              reset() the closest &lt;form /&gt;
              hide the closest &lt;form /&gt;
              show the previous &lt;h1 /&gt;&quot; /&gt;
&lt;/form&gt;
</code></pre>
<p>In Alpine.js, everything is a ‘component’. To create a component, we need an enclosing element and add an <a href="https://alpinejs.dev/directives/data"><code>x-data</code> attribute</a> to it. This also defines the initial state of the component. The main functionality we’re looking for is toggling between two elements, so we can represent that with a boolean.</p>
<pre><code class="language-html">&lt;div x-data=&quot;{ edit: false }&quot;&gt;
  &lt;h1&gt;
    Title
  &lt;/h1&gt;
  &lt;form&gt;
    &lt;input value=&quot;Title&quot; /&gt;
  &lt;/form&gt;
&lt;/div&gt;
</code></pre>
<p>To show or not show elements, we can use the <a href="https://alpinejs.dev/directives/show"><code>x-show</code> attribute</a>, which lets you declare, in JavaScript, when an element should be visible.</p>
<pre><code class="language-html">&lt;div x-data=&quot;{ edit: false }&quot;&gt;
  &lt;h1 x-show=&quot;!edit&quot;&gt;
    Title&lt;/h1&gt;
  &lt;form x-show=&quot;edit&quot;&gt;
    &lt;input value=&quot;Title&quot; /&gt;
  &lt;/form&gt;
&lt;/div&gt;
</code></pre>
<p>The <code>h1</code> is visible when <code>edit</code> is false, and the <code>form</code> is visible when <code>edit</code> is true. Now to set it to true when you click the <code>h1</code>, and to false when you focus away from the input. We can do that by handling events with <a href="https://alpinejs.dev/directives/on"><code>x-on</code> attributes</a>.</p>
<pre><code class="language-html">&lt;div x-data=&quot;{ edit: false }&quot;&gt;
  &lt;h1 x-show=&quot;!edit&quot;
      x-on:click=&quot;edit = true&quot;&gt;
    Title&lt;/h1&gt;
  &lt;form x-show=&quot;edit&quot;&gt;
    &lt;input value=&quot;Title&quot;
           x-on:blur=&quot;edit = false&quot; /&gt;
  &lt;/form&gt;
&lt;/div&gt;
</code></pre>
<p>Here, <code>x-on:click</code> means ‘when clicked, execute this JavaScript’. You can guess what <code>x-on:blur</code> does.</p>
<p>Now we just need the finishing touches: focusing the text field when we start editing, and resetting its value when the user cancels the edit. Because the <code>x-on</code> attribute contains JavaScript, we can just add a semicolon and add more statements. But how do we focus on the text field from the <code>h1</code>’s event handler? Enter the <a href="https://alpinejs.dev/directives/ref"><code>x-ref</code> attribute</a>; it lets you give elements addressable names within the scope of a component. The way you access them is through the magic<a id="fnref:4" href="#fn:4" class="footnote-ref"><sup>4</sup></a> <a href="https://alpinejs.dev/magics/refs"><code>$refs</code> property</a>.</p>
<pre><code class="language-html">&lt;div x-data=&quot;{ edit: false }&quot;&gt;
  &lt;h1 x-show=&quot;!edit&quot;
      x-on:click=&quot;edit = true; $refs.input.focus()&quot;&gt;
    Title&lt;/h1&gt;
  &lt;form x-show=&quot;edit&quot;
        x-ref=&quot;form&quot;&gt;
    &lt;input value=&quot;Title&quot;
           x-on:blur=&quot;edit = false; $refs.form.reset()&quot;
           x-ref=&quot;input&quot; /&gt;
  &lt;/form&gt;
&lt;/div&gt;
</code></pre>
<p>I appreciate that this is somewhat easier to understand, particularly the <code>$refs</code> part. Does it work and fit with htmx? Yes, absolutely. I personally prefer hyperscript for its procedural approach, but if you like the likeness to JavaScript and the reactiveness, this might be a better fit.</p>
<h2 id="conclusion-reflection">Conclusion, reflection</h2>
<p>And there you have it: a clone of Trellix, which is itself a clone of Trello, implemented mostly using htmx, with a sprinkling of JavaScript and hyperscript where it makes sense. I’ve certainly learned a thing or two about htmx, hyperscript, Alpine.js, and even HTML and CSS.</p>
<p>The original premise of this article was to respond to the statement that it would be ‘so hard’ to build something like Trellix using htmx, and I hope I’ve managed to dispel that claim.</p>
<p>I don’t think it’s possible to truly compare the codebase to that of Trellix, because there are too many differences, but I will say that I like the fact that my implementation contains a total of 24 lines of JavaScript (excluding libraries, of course) and 25 lines of hyperscript.</p>
<p>Now, while my version does not look as slick as Trellix, there’s a surprising amount of polish considering there are <a href="https://github.com/heemskerkerik/htmello/blob/main/wwwroot/css/site.css">only 116 lines of CSS</a>. Of course it’s leaning quite a bit on the shoulders of Bootstrap, but still, I’m impressed with the state of CSS in 2024.</p>
<p>Regarding the build process, the <a href="https://github.com/heemskerkerik/htmello/blob/main/htmello.csproj">project file</a> is 12 lines long, and literally the only change from the output of <code>dotnet new</code> was to add Khalid Abuhakmeh’s tiny <a href="https://github.com/khalidabuhakmeh/Htmx.Net">Htmx.Net package</a>, which adds some convenience methods to work with request and response headers.</p>
<h3 id="what-about">What about…</h3>
<p>I’m sure there are a lot of details of Trellix’ implementation that my version doesn’t capture; animations and such, or visual refinements. That was not the point of this article. I don’t want to be too hand-wavy about such details, but in my opinion, they fall into one of these buckets:</p>
<ol>
<li>They are easily implemented by using HTML and CSS, or a light sprinkling of hyperscript or (if you must) JavaScript.</li>
<li>They’re cumbersome or impossible to implement (although the latter is improbable).</li>
</ol>
<p>For the use case of Trellix, details that fall into the second bucket are generally not <em>really</em> that important for the customer experience, and certainly not worth the trouble of setting up a whole SPA application.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>The actual application uses a lot of CSS classes that would obviously make this article even harder to understand, and it uses some tag helpers for type safety and brevity. I opted to not use tag helpers in the article to make it easier to understand for those unfamiliar with them.<a href="#fnref:1" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>Despite a lot of progress in the right direction, it’s not yet possible to use a standardized element or attribute, like <code>&lt;dialog&gt;</code> or <code>popover</code> to do this.<a href="#fnref:2" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:3">
<p>Thanks to <a href="https://old.reddit.com/r/htmx/comments/1hkmxc1/making_a_trello_clone_using_htmx/m3fkjg8/">u/Trick_Ad_3234</a> for spotting the mistake there.<a href="#fnref:3" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:4">
<p>Alpine.js’ documentation literally calls these properties ‘magic properties’.<a href="#fnref:4" class="footnote-back-ref">&#8617;</a></p>
</li>
</ol>
</div>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2016-12-13-Achtergrond-3.2.svg.png" medium="image" /></item><item><title>htmx: Simplicity in an Age of Complicated Solutions</title><description>In an age of complicated front-end solutions, is there a simpler way of doing things? Spoiler alert: there is.</description><link>https://www.erikheemskerk.nl/htmx-simplicity/</link><guid isPermaLink="false">dcd94e0582ae4085bad7c05c808d712f</guid><category>htmx</category><category>Hypermedia</category><category>HTML</category><category>Front-end</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Thu, 30 May 2024 08:00:00 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2018-03-01-Achtergrond-7.1.svg.png" alt="htmx: Simplicity in an Age of Complicated Solutions" /><p>Almost four decades ago, Fred Brooks, software engineer and writer of <em>The Mythical Man-Month</em>, <a href="http://www.cs.unc.edu/techreports/86-020.pdf">wrote</a>:</p>
<blockquote>
<p>There is inherently no silver bullet.</p>
</blockquote>
<p>He was writing about complexity in the building of software, and how there is no conceivable way to make the process go significantly faster. He also classified two kinds of complexity encountered when building software: <em>essential</em> complexity, which is inherent to the problem the software is trying to solve; and <em>accidental</em> complexity, which is complexity introduced by the choices we make in the development process. An example of accidental complexity is that of reusability; you don’t want to write the same piece of code twice, because you don’t want to have to make changes in two (or more) places when the piece of code needs changing.</p>
<p>The term ‘silver bullet’ is also used derogatorily to refer to the fact that there is no single technology or piece of software that is universally applicable in all situations, or even more than a handful. The ‘silver bullet’ technology is a dream that lingers stubbornly in the minds of developers; who wouldn’t want to stumble upon that one technology that magically solves all of your problems? However, as Fred Brooks has eloquently stated, there is no such thing. The industry seems to struggle to learn this.</p>
<p>Candidates for the ‘silver bullet’ often focus on accidental complexity, trying to make the developers’ lives easier, but as another evergreen phrase says, <em>there’s no such thing as a free lunch</em>, meaning everything comes with a cost or a trade-off. In my opinion, nowadays, the cost often being paid is that of <em>simplicity</em>; while a particular solution makes it easier to reuse code, it might also make it more complicated.</p>
<h2 id="front-end-complexity">Front-end complexity</h2>
<p>Front-end development is rife with examples of this. The engineering culture among many front-end developers seems to be one where <em>simplicity</em> is a dirty word. There are towering levels of abstraction, comically complicated and long build processes, bad performance, horrible tooling, and buggy applications, and everybody seems fine with this.</p>
<p><img src="/content/images/2024/this_is_fine.gif" alt="Animated version of the ‘This is fine’ meme" /></p>
<p>Let’s look at an example. We want to add search functionality to our site. Using nothing more than HTML 5, we can create a form with a text field.</p>
<pre><code class="language-html">&lt;form method=&quot;GET&quot; action=&quot;/search&quot;&gt;
  &lt;input type=&quot;search&quot; name=&quot;query&quot; placeholder=&quot;Search&quot; /&gt;
  &lt;input type=&quot;submit&quot; value=&quot;Search&quot; /&gt;
&lt;/form&gt;
</code></pre>
<p><img src="/content/images/2024/search_form.png" alt="Search form in HTML5" /></p>
<p>This is very basic, but it gets the job done. When you type something in the field and press Enter or click the Search button, your query is sent to the server and the response, hopefully containing some search results, replaces the entire page. The fact that it replaces the entire page can be jarring. When you have a slow or bad connection (which everyone who’s ever used hotel or train WiFi can confirm is still a thing) the page temporarily stops being interactive while blocking resources are being loaded. When it finishes loading, you’ll have lost all state on the page, such as scroll position, data entered in form fields, collapsed or expanded elements, and so on.</p>
<p>Changing only a part of the page to display the search results would result in a much better user experience. Unfortunately, this is not part of HTML. JavaScript can help us out here, surely.</p>
<h2 id="vanilla-javascript">Vanilla JavaScript</h2>
<p>Let’s just add a <code>&lt;script&gt;</code> tag with some ‘vanilla’ JavaScript code to make the process asynchronous. Naturally, we’re going to request some JSON from a ‘REST endpoint’. Not GraphQL. That would be over-engineering.</p>
<pre><code class="language-jsx">&lt;input type=&quot;search&quot; id=&quot;searchInput&quot; placeholder=&quot;Search...&quot;&gt;
&lt;button id=&quot;searchButton&quot;&gt;Search&lt;/button&gt;
&lt;ul id=&quot;resultsList&quot;&gt;&lt;/ul&gt;

&lt;script&gt;
  const resultsList = document.getElementById('resultsList');
  const searchInput = document.getElementById('searchInput');
  const searchButton = document.getElementById('searchButton');

  searchButton.addEventListener('click', handleSearch);
  searchInput.addEventListener('keypress', function(event) {
    if (event.key === 'Enter') {
      handleSearch();
    }
  });

  async function handleSearch() {
    const searchTerm = searchInput.value.trim();
    const encodedSearchTerm = encodeURIComponent(searchTerm);
    const apiUrl = `/search?query=${encodedSearchTerm}`;
    resultsList.innerHTML = ''; // Clear previous results

    const response = await fetch(apiUrl);

    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();

    if (data.length === 0) {
      resultsList.innerHTML = 'No results found.';
    } else {
      data.forEach(result =&gt; {
        const listItem = document.createElement('li');
        listItem.textContent = result.title;
        resultsList.appendChild(listItem);
      });
    }
}
&lt;/script&gt;
</code></pre>
<p>Yikes. There is so much more here than in the HTML version. A lot of the code is focused on <em>translation</em>. Translation between the raw search query and a URL. Translation between the (assumed) JSON response and DOM elements. Look at how much we need to do; grabbing and creating DOM elements, adding event handlers, handling keystrokes…</p>
<p>This is a very limited example with many problems:</p>
<ul>
<li>The representation of search results is so basic it’s not even a link;</li>
<li>It works pretty well when you have awesome network conditions (say, between your browser and your local host), but when you’re using hotel WiFi, it would be nice if there was some sort of loading indicator;</li>
<li>If you initiate a new request while another one is still in flight, it would be nice if the older one is canceled or at least discarded.</li>
</ul>
<p>Note that there is an implied contract between this code running in the browser and the code on the server: the shape of the response. The code assumes the response is JSON containing an array of objects, where each object has a property called <code>title</code>. If that is somehow not the case, it will appear to the user as if nothing happened (because there is no error handling).</p>
<p>Dealing with the loading indicator and aborting in-flight requests takes a bit of extra code, but having <em>readable</em> code for the layout of results is a stretch. Let’s just pull out all the stops and use React!</p>
<h2 id="react">React</h2>
<pre><code class="language-jsx">import React, { useState, useEffect } from 'react';

function Search() {
  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const [abortController, setAbortController] = useState(null);

  const handleSearch = async () =&gt; {
    if (abortController) {
      // Abort the previous request if there is one
      abortController.abort();
    }

    if (searchTerm.trim() === '') {
      setResults([]);
      return;
    }

    const encodedSearchTerm = encodeURIComponent(searchTerm.trim());
    const apiUrl = `/search?q=${encodedSearchTerm}`;

    setResults([]);
    setLoading(true);

    const controller = new AbortController();
    setAbortController(controller);

    try {
      const response = await fetch(apiUrl, { signal: controller.signal });

      if (controller.signal.aborted) {
        // Request was aborted, so no further action is needed
        return;
      }

      if (!response.ok) {
        throw new Error('Network response was not ok');
      }

      const data = await response.json();
      setResults(data);
    } catch (error) {
      if (error.name !== 'AbortError') {
        // Display an error message only if it's not an abort
        console.error('Fetch error:', error);
      }
    } finally {
      setLoading(false);
    }
  };

  const handleKeyPress = (event) =&gt; {
    if (event.key === 'Enter') {
      handleSearch();
    }
  };

  useEffect(() =&gt; {
    return () =&gt; {
      // Cleanup function to abort the request if the component unmounts
      if (abortController) {
        abortController.abort();
      }
    };
  }, [abortController]);

  return (
    &lt;input
      type=&quot;search&quot;
      onChange={(e) =&gt; setSearchTerm(e.target.value)}
      onKeyPress={handleKeyPress}
    /&gt;
    &lt;button onClick={handleSearch}&gt;Search&lt;/button&gt;
    {loading &amp;&amp; &lt;div&gt;Loading...&lt;/div&gt;}
    &lt;ul&gt;
      {results.map((result, index) =&gt; (
          &lt;li key={`result-${index}`}&gt;&lt;a href={result.href}&gt;{result.title}&lt;/a&gt;&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}

export default Search;
</code></pre>
<p>Look at how massive this is becoming. To be fair, this isn’t even <em>that</em> much bigger than the pure JavaScript version. There’s a bunch of state declaration and handling, some error handling, and there’s a few lines of additional logic around the logic of aborting requests. I’d like to point out two things, though.</p>
<p>First, we now have a lot more state to manage, including some state that needs to be cleaned up when the component is removed from the DOM (or ‘unmounted’, in React-speak); that’s the <code>useEffect</code> call near the end.</p>
<p>Second, unlike the two earlier solutions, you can’t just plonk this in a file, access it using a <code>&lt;script&gt;</code> tag, and expect it to work. Essentially, you need to transpile this <em>kind of</em> JavaScript file into an <em>actual</em> JavaScript file. To be able to do that you need to set up a React application. This means running an NPM script that sets up a complicated directory structure, which is configured so you can run a command-line application to start a web server, which can then serve your React application. Even after you do a so-called production build, you’re left with an HTML file with placeholders and script tags and a whole bunch of JavaScript files called chunks, which contain tiny parts of your application and the required third-party libraries it needs. All the parts only make sense at run-time, where they hopefully all combine and work.</p>
<p>And for all of its complexity, this version of the search box <em>still</em> has limitations. It doesn’t support incremental search, which is where searching is continually performed while you’re typing, without losing focus or having to click a button. In web applications this is generally not done on every keypress, but only when the user stops typing for a fraction of a second. Another problem is that when you have searched, the URL of the page is not updated. This means it’s not possible to share your search query, bookmark it, or open it in a new browser tab. This flaw is shared by the pure JavaScript version, but not by the simple HTML version because that simply navigates to a different URL.</p>
<p>While it is certainly possible to overcome all of these issues in either the pure JavaScript or React versions of the code, we all understand the code would become even more complicated. Instead, let’s look at an approach that, instead, focuses on the HTML side of things, without piling on more and more JavaScript.</p>
<h2 id="htmx">Htmx</h2>
<p><a href="https://htmx.org">Htmx</a> is a tiny JavaScript library that lets you add Fetch functionality to HTML elements by using nothing more than HTML attributes. You can let a standard <code>&lt;button&gt;</code> element make an HTTP request when it’s clicked and handle the response by just a few attributes.</p>
<pre><code class="language-html">&lt;button hx-post=&quot;/transfer/all?to=randomCharity&quot; 
        hx-target=&quot;#thanks&quot;&gt;
  Empty my bank account
&lt;/button&gt;
&lt;div id=&quot;thanks&quot;&gt;&lt;/div&gt;
</code></pre>
<p>The <code>hx-post</code> attribute here defines that when that button is clicked, a <code>POST</code> request is made to that URL. The <code>hx-target</code> attribute defines where to display the response as a <a href="https://drafts.csswg.org/selectors/">CSS selector</a>; in this case in an element with an <code>id</code> attribute equal to <code>thanks</code>. So how does htmx know what HTML elements to create from the response? A good question with a simple answer: the response <strong>is HTML</strong>. That way, your browser does not need to run code to transform the data it receives from a back-end system into DOM elements—it just receives HTML, which it already knows how to deal with.</p>
<h2 id="hypermedia">Hypermedia</h2>
<p>Unlike most SPA frameworks, htmx is not trying to run an entire application in the browser, but is instead trying to keep the browser ‘dumb’. It does this by utilizing a decades-old concept called <em>hypermedia</em>. What’s hypermedia again? According to the book <a href="https://hypermedia.systems">Hypermedia Systems</a>, by the authors of htmx:</p>
<blockquote>
<p>Hypermedia is a media, for example, a text, that includes <em>non-linear branching</em> from one location in the media to another, via, for example, hyperlinks embedded in the media.</p>
</blockquote>
<p>The crucial bit here is <em>embedded in the media</em>: a hypertext (a text that is hypermedia, such as HTML) contains all the information about possible interactions encoded within the text. Think of a search result page in Google a couple of years ago; you knew there were more pages available because there was a ‘next page’ link. There’s no need for you or your browser to understand what the URL of the next page is: that is embedded in the HTML your browser received from Google. If you change your URL structure, you only need to change it in one application. If you add a new feature, say navigating not to the next page but the one five pages over, it is as simple as adding another hyperlink.</p>
<h2 id="rest-done-poorly">REST, done poorly</h2>
<p>For those of you who have been developing Web APIs for a while, this might cause a few bells to ring. A concept that is still often heard when talking about Web APIs is HATEOAS: Hypertext As The Engine Of Application State. This is a constraint for RESTful APIs that is derived from a few disparate concepts in Roy Fielding’s dissertation on Representational State Transfer. These concepts are <em>stateless communication</em> and <em>self-descriptive messages</em>, and the idea that clients should be able to navigate and interact with applications using only the information provided by the server, without requiring hard-coded knowledge or out-of-band information. This is precisely the thing that hypermedia solves.</p>
<p>REST, or Representational State Transfer, was originally conceived as an architectural style for distributed applications. Nowadays ‘REST’ usually refers to a JSON API that has an opinion on how URLs are constructed and that uses more verbs than <code>GET</code> and <code>POST</code>. The thing is: JSON, in and of itself, does not facilitate <em>hypermedia controls</em> (elements that allow for this non-linear branching), so most JSON APIs are not, in fact, RESTful.</p>
<p>Efforts have been made to make JSON APIs more RESTful by adding links to documents. One of the most familiar ones is HAL, or Hypertext Application Language. It adds a well-known property to all JSON documents and even within elements of a document. Imagine the response to our search operation looking like this:</p>
<pre><code class="language-json">{
  &quot;_links&quot;: {
    &quot;self&quot;: &quot;https://example.org/api/search?q=complexity&amp;p=4&quot;,
    &quot;first&quot;: &quot;https://example.org/api/search?q=complexity&quot;,
    &quot;prev&quot;: &quot;https://example.org/api/search?q=complexity&amp;p=3&quot;,
    &quot;next&quot;: &quot;https://example.org/api/search?q=complexity&amp;p=5&quot;
    &quot;last&quot;: &quot;https://example.org/api/search?q=complexity&amp;p=42&quot;
  },
  &quot;_embedded&quot;: {
    &quot;results&quot;: [
      {
        &quot;_links&quot;: {
          &quot;self&quot;: &quot;http://www.cs.unc.edu/techreports/86-020.pdf&quot;
        },
        &quot;title&quot;: &quot;No Silver Bullet: Essence and Accidents of Software Engineering&quot; 
      },
      // ...
    ]
  }
}
</code></pre>
<p>This describes how to get to the first page, the next page, and how to navigate to each of the search results. It does not, for example, describe which page you’re on, how many pages there are in total, or how to get to the seventeenth page. Yes, you could add URLs for all of the pages, but now you need <em>hard-coded knowledge</em> in the client to deal with these links. At least <code>first</code>, <code>prev</code>, <code>next</code>, and <code>last</code> are kind of standardized names of links, but to provide links to arbitrary pages you’d need to create a custom scheme, something like <code>page:17</code>. Which needs more hard-coded knowledge for it to be usable.</p>
<p>Another solution would be to add a link ‘template’ that describes how the URL for a particular page is constructed. Something like this:</p>
<pre><code class="language-json">{
  &quot;_links&quot;: {
    &quot;template:page&quot;: &quot;https://example.org/api/search?q=complexity&amp;p=%d&quot;
  }
}
</code></pre>
<p>Now you need <em>hard-coded knowledge</em> in the client telling it to look for this link and to replace the placeholder <code>%d</code> with the page number it wants.</p>
<p>And the limitations go on: imagine how you would describe something as <em>trivial</em> as a search form in this fashion. You’d need all kinds of placeholders and magic values that need to somehow mean something to the client application. Truly RESTful JSON APIs are sometimes possible, but they’re very limited and brittle.</p>
<h2 id="rest-done-proper">REST, done proper</h2>
<p>HTML, on the other hand, is true hypermedia, so it’s almost trivial to create a RESTful application using only HTML. HTML has its limitations, as we discussed, and we can overcome most of those using a library like htmx. It’s easy to imagine what our search results would look like in HTML.</p>
<pre><code class="language-html">&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cs.unc.edu/techreports/86-020.pdf&quot;&gt;No Silver Bullet: Essence and Accidents of Software Engineering&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;nav&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;/search?q=complexity&quot;&gt;&amp;laquo; First&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;/search?q=complexity&amp;amp;p=3&quot;&gt;3&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;4&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;/search?q=complexity&amp;amp;p=5&quot;&gt;5&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;/search?q=complexity&amp;amp;p=42&quot;&gt;Last &amp;raquo;&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
</code></pre>
<p>When this is rendered by a browser, even without any styling, it is fully functional. The hyperlinks just work, because they are self-descriptive. If we want to add a search box to be able to manually enter the page number, we can do just that, without even getting fancy. We just add the following fragment into the <code>&lt;nav&gt;</code> element:</p>
<pre><code class="language-html">&lt;form method=&quot;get&quot; action=&quot;/search&quot;&gt;
  &lt;input type=&quot;hidden&quot; name=&quot;q&quot; value=&quot;complexity&quot; /&gt;
  &lt;input type=&quot;number&quot; name=&quot;p&quot; min=&quot;1&quot; max=&quot;42&quot; /&gt;
&lt;/form&gt;
</code></pre>
<p>How simple! And because of the power of HTML, this <em>just works</em>. You enter a number, press Enter, and it goes. Hidden fields are used to transfer all of the state of the application, and it’s up to the browser to render the correct user interface elements and make them behave appropriately. Authentication is done the same way it is for viewing any page because it <em>is a page</em>. Caching works out of the box, assuming you’ve set your response headers correctly.</p>
<p>The remaining problem is that this still refreshes the entire page, and that causes a loss of state. Let’s remedy that using htmx.</p>
<h2 id="the-power-of-attributes">The power of attributes</h2>
<p>As we touched on before, htmx <em>enhances</em> HTML by defining new attributes that give each element the power to trigger Fetch requests, which result in elements being swapped in. These attributes let you define, among others:</p>
<ul>
<li>How and where to make the request; this is done by attributes like <code>hx-get</code>, <code>hx-post</code>, <code>hx-put</code>, <code>hx-delete</code>, and so on.</li>
<li>What to <em>swap</em> in the page; primarily, this is directed by <code>hx-target</code>. <code>hx-swap</code> lets you define <em>how</em> to swap the content; should the inner HTML of the target be replaced, or the <em>outer</em> HTML (i.e. replace the entire element), or should it be added inside or after the target?</li>
<li>When to make the request. The default trigger depends on the element. For example, text boxes and drop-down lists are triggered by their value changing, forms are triggered by the form being submitted, and everything else is triggered by a click. You can use <code>hx-trigger</code> to specify a different event to trigger on, or even multiple ones, and add modifiers, such as delays or only triggering when the value has changed.</li>
<li>Which part of the response to use. By default, everything is used, but if you only want to use a specific part of the response, you can use <code>hx-select</code>.</li>
<li>Whether to push a URL into the browser’s history, replacing the URL in the address bar. This way your application state is easily bookmarked or shared. <code>hx-push-url</code> will let you push either the request URL or a custom URL into the browser history.</li>
</ul>
<p>There are a bunch more <a href="https://htmx.org/reference/">attributes</a> (though not a ton), but these are the core ones that will solve most problems. So how can we apply this to, for example, a pagination link?</p>
<p>Let’s imagine we don’t change anything about the way our application renders pages. That means <code>/search?q=complexity</code> will return a full HTML page with the first page of results, and <code>/search?q=complexity&amp;p=2</code> will return a full HTML page with the second page of results. We’ll put the search results into a <code>&lt;section id=&quot;results&quot;&gt;</code> element because that gives us an easy target. This is what an individual page link looks like:</p>
<pre><code class="language-html">&lt;li&gt;
  &lt;a hx-get=&quot;/search?q=complexity&amp;amp;p=3&quot; 
     hx-select=&quot;section#results&quot;
     hx-target=&quot;section#results&quot;
     hx-swap=&quot;outerHTML&quot;
     hx-push-url=&quot;true&quot;&gt;3&lt;/a&gt;
&lt;/li&gt;
</code></pre>
<p>These five attributes define the following behavior:</p>
<ul>
<li><code>hx-get</code>: When this link is clicked (or otherwise activated), make a <code>GET</code> request to <code>/search?q=complexity&amp;p=3</code>.</li>
<li><code>hx-select</code>: From the response to that, select a <code>&lt;section&gt;</code> element with the ID <code>results</code>.</li>
<li><code>hx-target</code>: Swap that into the <code>&lt;section&gt;</code> element with the ID <code>results</code> currently on the page.</li>
<li><code>hx-swap</code>: Replace the outer HTML of the target. In other words, replace the entire element.</li>
<li><code>hx-push-url</code>: Push the URL <code>/search?q=complexity&amp;p=3</code> into the browser history and the address bar.</li>
</ul>
<p>And that’s it! Now the pagination links replace only the part of the page they affect. We’ve had to write exactly zero JavaScript for it, and our application is still RESTful.</p>
<p>But wait, it gets even better. Htmx supports <em>progressive enhancement</em>. This means that you can start with basic, HTML-only functionality, and only if the browser allows JavaScript and the htmx library has been loaded, will its full functionality be enabled. This also makes it easier for search engines to understand your page structure. It looks like this:</p>
<pre><code class="language-html">&lt;li&gt;
  &lt;a href=&quot;/search?q=complexity&amp;amp;p=3&quot; 
     hx-boost=&quot;true&quot;
     hx-select=&quot;section#results&quot;
     hx-target=&quot;section#results&quot;
     hx-swap=&quot;outerHTML&quot;&gt;3&lt;/a&gt;
&lt;/li&gt;
</code></pre>
<p>Instead of <code>hx-get</code>, we have a normal <code>href</code> attribute, and because we’ve defined <code>hx-boost=true</code> on an anchor tag, htmx knows to make a <code>GET</code> request to the URL in the <code>href</code> attribute and to push the URL into the browser history.</p>
<h2 id="search-in-htmx">Search in htmx</h2>
<p>If you’re still here, kudos. Now let’s circle back to the search example we’ve been looking at before and see what that would look like using htmx.</p>
<pre><code class="language-html">&lt;input type=&quot;search&quot;
       name=&quot;q&quot;
       placeholder=&quot;Search&quot;
       hx-get=&quot;/search&quot;
       hx-target=&quot;section#searchResults&quot;
       hx-select=&quot;section#searchResults&quot;
       hx-trigger=&quot;search, click from:#searchButton&quot;
       hx-indicator=&quot;#searchLoading&quot;
       hx-sync=&quot;this:replace&quot; /&gt;
&lt;button id=&quot;searchButton&quot;&gt;Search&lt;/button&gt;
&lt;div id=&quot;searchLoading&quot; class=&quot;htmx-indicator&quot;&gt;Loading...&lt;/div&gt;
&lt;section id=&quot;searchResults&quot;&gt;
  &lt;!-- put search results here --&gt;
&lt;/section&gt;
</code></pre>
<p>Hopefully, you understand what the <code>hx-get</code>, <code>hx-target</code>, and <code>hx-select</code> elements are doing here.</p>
<p><code>hx-trigger</code> is defining when the requests are triggered. In this case, that is on the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/search_event"><code>search</code> event</a>, or when a <code>click</code> event is fired by the element with ID <code>searchButton</code>. Next, <code>hx-indicator</code> sets an element that should be shown whenever this element has a request in flight. Finally, <code>hx-sync</code> defines that if any new requests are triggered while there is still one in flight, the one in flight is replaced by the new one.</p>
<p>This is functionally identical to the React version. However, using htmx, we can also get rid of those limitations I mentioned earlier: incremental search and updating the URL in the address bar.</p>
<pre><code class="language-html">&lt;input type=&quot;search&quot;
       name=&quot;q&quot;
       placeholder=&quot;Search&quot;
       hx-get=&quot;/search&quot;
       hx-target=&quot;section#results&quot;
       hx-select=&quot;section#results&quot;
       hx-trigger=&quot;search, click from:#searchButton, keyup changed delay:300ms&quot;
       hx-indicator=&quot;#searchLoading&quot;
       hx-sync=&quot;this:replace&quot;
       hx-push-url=&quot;true&quot; /&gt;
</code></pre>
<p>The only new things are an additional trigger and the <code>hx-push-url</code> attribute. The new trigger, <code>keyup changed delay:300ms</code>, means the request should be triggered on ‘key up’ events, but only when the value of the field has changed, and only when no new key-up requests have been received for 300 milliseconds. This is incremental searching, but without sending a barrage of requests to the application, which is what would happen if you sent a request for every keystroke. Finally, because <code>hx-push-url</code> is set to <code>true</code>, the final URL of the request is set as the new location in the address bar, and the previous address is added to the browser history. If you’ve ever had to implement pushing and popping browser history, you’ll recognize how simple htmx’s solution is.</p>
<h2 id="howl">HOWL</h2>
<p>If you have a front end implemented largely in JavaScript, then the following argument is likely to have reared its head:</p>
<blockquote>
<p>Let’s use JavaScript on the back end as well, so we can reuse code, and front-end developers can work on the back end as well.</p>
</blockquote>
<p>I loathe this argument, for several reasons.</p>
<p>First of all, JavaScript. JavaScript is not a great language for anything except inside the browser. It never was and it likely never will be, unless it gets a decent standard library and ditches its type coercion and many other broken language features. No amount of developer discipline and linting can <em>truly</em> mitigate this. Unfortunately, we’re kind of stuck with JavaScript in the browser, but let’s not use it anywhere else if we can help it.</p>
<p>Second of all, reusing code between the front end and the back end. It sounds like a good idea, but a web server responding to requests uses a very different application model than code running in a browser responding to user interaction. To reuse the code almost certainly means forcing one of the two into abstractions that don’t make a lot of sense. On top of that, because of the different application models and many more differences, there’s a substantial learning curve between front-end and back-end development.</p>
<p>The more you invest in JavaScript on the front end, the more the pressure of this argument will grow.</p>
<p>Fortunately, when using htmx, you won’t need to engage in this argument, since you’ll hardly write any JavaScript on the front end. You can use any server-side language and framework you like. There’s no <em>pressure</em> to use JavaScript if that isn’t preferred. Is most of your existing code base in Python? Django or Flask both integrate very well with htmx. Do you like Java or .NET? Maybe Ruby or Rust? All of these and more have excellent options to create HTML-based applications, and it’s trivial to add htmx to them. This is referred to by the htmx community as HOWL: Hypermedia On Whatever you Like.</p>
<h2 id="silver-bullet">Silver bullet?</h2>
<p>I hope you’ll agree that htmx results in a simpler solution than a SPA framework, and that is worth <em>a lot</em>. Carson Gross (the developer behind htmx) talks about the <a href="https://htmx.org/essays/complexity-budget/">Complexity Budget</a>, or in other words: where in your application you allow complexity to exist. For most applications, the front-end should be just that, the front-end. What differentiates your application from others is usually the back-end functionality. Saving the complexity budget for the back-end, rather than spending it in the front-end, makes a lot of sense.</p>
<p>However, as this article starts, there is no such thing as a silver bullet. Htmx does not work in all situations. When a lot of your application is graphical (like a mapping or drawing app) or when a large portion of your UI needs to be redrawn in response to user input (like a spreadsheet or word processor), then you need a JavaScript-based solution. When your app needs to be available even when the user is offline, you’ll need a JavaScript-based solution. Fortunately, the vast majority of applications are just text and images and do not suffer from these limitations. For that vast majority, htmx is likely a good solution, and probably a better one than any of the currently popular SPA frameworks.</p>
<p>Have a look at the <a href="https://htmx.org">htmx site</a>, where there is a lot of good <a href="https://htmx.org/docs/">documentation</a>, as well as a collection of <a href="https://htmx.org/essays/">essays</a> that do a much better job of explaining the merits than this brief article.</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2018-03-01-Achtergrond-7.1.svg.png" medium="image" /></item><item><title>Reporting NUKE build failures on Slack</title><description>Let’s make our NUKE builds report failures by themselves, using a little reflection magic.</description><link>https://www.erikheemskerk.nl/reporting-nuke-build-failures-slack/</link><guid isPermaLink="false">296e3e31ba1d4d4c967dfc511c698ce2</guid><category>NUKE</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Wed, 25 Aug 2021 05:24:00 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2021/08/2020-10-08%20Achtergrond%209-1.png" alt="Reporting NUKE build failures on Slack" /><p>At Coolblue, we run a lot of builds and deployments. Because we practice <abbr title="Continuous Deployment">CD</abbr>, they all run automatically. This means that after we merge a pull request, our build server can spend up to half an hour building, testing, deploying, and validating. I don’t know about you, but I do not have enough attention span to watch slowly advancing progress bars for half an hour. So, instead, when <a href="https://www.erikheemskerk.nl/nuke-build-components">one of the build or deployment stages</a> fails, we’d like to get notified of this. Preferably on Slack, our messaging platform of choice, although this article could apply to any mechanism. Sure, most build servers have integrations with most messaging platforms nowadays, but what if you didn’t, or don’t want to rely on those?</p>
<p>You don’t want to have to write <code>try</code>/<code>catch</code> blocks all over the place, so it’d have to be something that you can either inject, or just run some code when the build is finished. Some build frameworks make this very easy. For NUKE, it’s as simple as overriding the <code>OnBuildFinished</code> method.</p>
<pre><code class="language-csharp">class Build: NukeBuild
{
  protected internal override void OnBuildFinished()
  {
    if (!IsSuccessful)
    {
      SlackTasks.SendSlackMessage(/* omitted for brevity */);
    }
  }
}
</code></pre>
<p>Easy. But how do we make this reusable? We don’t want people to have to copy and paste entire blocks of code. It would be nicer if it can just be ‘plugged in’.</p>
<h2 id="targets">Targets</h2>
<p>In <a href="https://www.erikheemskerk.nl/nuke-build-components/">my previous article</a>, I showed how NUKE’s build components make it easy to assemble a build script using only the components that you need. Adding a new target to the system is trivial, as is wiring it up to other targets.</p>
<p>Since we want our target to execute after the build finishes, we can just tack it onto our ‘main’ targets, right? Note that I’m simplifying the type structure for brevity.</p>
<pre><code class="language-csharp">class Build: NukeBuild
{
  Target BuildRelease =&gt;
    _ =&gt; _.Executes(() =&gt; { });

  Target Release =&gt;
    _ =&gt; _.Executes(() =&gt; { });

  Target NotifyFailureOnSlack =&gt;
    _ =&gt; _.AssuredAfterFailure() // always run, regardless of other targets failing
          .TryTriggeredBy&lt;Build&gt;(b =&gt; b.BuildRelease, b =&gt; b.Release)
          .Executes(
            () =&gt; 
            {
              // notify if build failed
            }
          )
}
</code></pre>
<p>Unfortunately, things are not that simple. The first obstacle is detecting the build has failed. <code>IsSuccessful</code> won’t work, because it always returns false, since the build <em>is still in progress</em>. You’d need to look at <code>ExecutingTargets</code> to see if any of them has failed.</p>
<p>There’s a much bigger problem, though. <a href="https://nuke.build/docs/authoring-builds/fundamentals.html#execution-dependencies">NUKE can execute targets in any order it wants</a>, unless you’ve <a href="https://nuke.build/docs/authoring-builds/fundamentals.html#ordering-dependencies">specifically told it to execute a particular target before or after another one</a>. This means if <code>BuildRelease</code> itself also triggers a different target, it could end up being run after <code>NotifyFailureOnSlack</code>. If that target then fails, it will go unnoticed. This is clearly not the right solution.</p>
<h2 id="attributes">Attributes</h2>
<p>An aspect of NUKE that is not documented at all on the official website is build extension attributes. When you setup a NUKE project, you’ll notice that the generated <code>Build</code> class is decorated with a bunch of different attributes.</p>
<pre><code class="language-csharp">[CheckBuildProjectConfigurations]
[ShutdownDotNetAfterServerBuild]
[DotNetVerbosityMapping]
class Build: NukeBuild
{
}
</code></pre>
<p>It turns out that these attributes are derived from a class called <a href="https://github.com/nuke-build/nuke/blob/2e3ebee5b041bb80d18d41a9da36b7e7d8fd28fc/source/Nuke.Common/Execution/BuildExtensionAttributeBase.cs#L20"><code>BuildExtensionAttributeBase</code></a> and that NUKE has code in place to recognize attributes that inherit from it as, well, extensions to the build class. There’s also a bunch of interfaces that represent ‘events’, such as <a href="https://github.com/nuke-build/nuke/blob/2e3ebee5b041bb80d18d41a9da36b7e7d8fd28fc/source/Nuke.Common/Execution/BuildExtensionAttributeBase.cs#L71"><code>IOnBuildFinished</code></a> that, when implemented on your build or a build extension, lets that component respond to build events. <code>OnBuildFinished</code> should sound familiar. Let us see what this looks like.</p>
<pre><code class="language-csharp">public class NotifyFailureOnSlackAttribute: BuildExtensionAttributeBase, IOnBuildFinished
{
  public void OnBuildFinished(NukeBuild build)
  {
    if (!build.IsSuccessful)
    {
      // notify
      Logger.Error($&quot;{nameof(NotifyFailureOnSlackAttribute)}: Build failed!&quot;);
    }
  }
}
</code></pre>
<p>Does it work?</p>
<pre><code class="language-plaintext">╬════════════
║ Failure
╬═══

&gt; /usr/local/share/dotnet/dotnet build DoesNotExist.csproj --verbosity Minimal
Microsoft (R) Build Engine version 16.10.1+2fd48ab73 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

MSBUILD : error MSB1009: Project file does not exist.
Switch: DoesNotExist.csproj
ProcessException: Process 'dotnet' exited with code 1.
   &gt; /usr/local/share/dotnet/dotnet build DoesNotExist.csproj --verbosity Minimal
   @ /Users/e.heemskerk/Git/Prive/NukeTest/build

   at Nuke.Common.Tooling.ProcessExtensions.AssertZeroExitCode(IProcess process)
   at Nuke.Common.Tools.DotNet.DotNetTasks.DotNetBuild(DotNetBuildSettings toolSettings)
   at Nuke.Common.Tools.DotNet.DotNetTasks.DotNetBuild(Configure`1 configurator)
   at Build.&lt;&gt;c.&lt;get_Failure&gt;b__2_1() in /Users/e.heemskerk/Git/Prive/NukeTest/build/Build.cs:line 21
   at Nuke.Common.Execution.TargetDefinition.&lt;&gt;c__DisplayClass61_0`1.&lt;Executes&gt;b__0()
   at Nuke.Common.Execution.BuildExecutor.&lt;&gt;c.&lt;Execute&gt;b__4_0(Action x)
   at Nuke.Common.Utilities.Collections.EnumerableExtensions.ForEach[T](IEnumerable`1 enumerable, Action`1 action)
   at Nuke.Common.Execution.BuildExecutor.Execute(NukeBuild build, ExecutableTarget target, IReadOnlyCollection`1 previouslyExecutedTargets, Boolean failureMode)

Repeating warnings and errors:
MSBUILD : error MSB1009: Project file does not exist.
ProcessException: Process 'dotnet' exited with code 1.
   &gt; /usr/local/share/dotnet/dotnet build DoesNotExist.csproj --verbosity Minimal
   @ /Users/e.heemskerk/Git/Prive/NukeTest/build

═══════════════════════════════════════
Target             Status      Duration
───────────────────────────────────────
Failure            Failed        &lt; 1sec
───────────────────────────────────────
Total                            &lt; 1sec
═══════════════════════════════════════

Build failed on 07/03/2021 14:10:17. (╯°□°）╯︵ ┻━┻
NotifyOnFailureAttribute: The build failed!
</code></pre>
<h2 id="error-messages">Error messages</h2>
<p>While this works, the message isn’t very useful. It doesn’t tell you <em>what</em> the failure was, or in <em>which</em> build it occurred. Let’s remedy that.</p>
<p>Unfortunately, getting error messages from NUKE isn’t that simple. Typically you’d do this by (also) writing log messages to an in-memory logger, which lets you sift through the messages it captured at your leisure. However, there is no public method or property that lets you do this; logging sinks are automatically configured by NUKE through an internal property.</p>
<p>You might have noticed the build output of our first attempt mentioned <code>Repeating warnings and errors</code>. This must mean it’s being stored somewhere, so maybe we can take advantage of that as well.</p>
<p>It turns out we can. The abstract class <code>OutputSink</code> has an internal field called <code>SevereMessages</code>, which is where warnings and errors are copied to as the build is running. How do you get an <code>OutputSink</code>? The <code>Logger</code> class has an internal static field called <code>OutputSink</code>. Given all these fields are internal, using reflection seemed the simplest approach to getting these messages out of there:</p>
<pre><code class="language-csharp">internal static class LoggerUtility
{
  public static OutputSink GetOutputSink() =&gt; (OutputSink) _outputSinkField.GetValue(null);

  static LoggerUtility()
  {
    _outputSinkField = typeof(Logger).GetField(_outputSinkFieldName, BindingFlags.Static | BindingFlags.NonPublic)
                    ?? throw new InvalidOperationException($&quot;Couldn't find '{_outputSinkFieldName}' field on type {nameof(Logger)}.&quot;);
  }

  private static readonly FieldInfo _outputSinkField;
  private const string _outputSinkFieldName = &quot;OutputSink&quot;;
}

internal static class OutputSinkExtensions
{
  public static string GetErrorMessages(this OutputSink sink)
  {
    var messages = (List&lt;Tuple&lt;LogLevel, string&gt;&gt;) _severeMessagesField.GetValue(sink);

    return string.Join(
      System.Environment.NewLine,
      messages!.Where(m =&gt; m.Item1 == LogLevel.Error)
               .Select(t =&gt; t.Item2)
    );
  }

  static OutputSinkExtensions()
  {
    _severeMessagesField = typeof(OutputSink).GetField(_severeMessagesFieldName, BindingFlags.Instance | BindingFlags.NonPublic)
                        ?? throw new InvalidOperationException($&quot;Couldn't find '{_severeMessagesFieldName}' field on type {nameof(OutputSink)}.&quot;);
  }

  private static readonly FieldInfo _severeMessagesField;
  private const string _severeMessagesFieldName = &quot;SevereMessages&quot;;
}
</code></pre>
<p>This should give us a relatively detailed description of what went wrong.</p>
<pre><code class="language-plaintext">╬════════════
║ Failure
╬═══

&gt; /usr/local/share/dotnet/dotnet build DoesNotExist.csproj --verbosity Minimal
Microsoft (R) Build Engine version 16.10.1+2fd48ab73 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

MSBUILD : error MSB1009: Project file does not exist.
Switch: DoesNotExist.csproj
ProcessException: Process 'dotnet' exited with code 1.
   &gt; /usr/local/share/dotnet/dotnet build DoesNotExist.csproj --verbosity Minimal
   @ /Users/e.heemskerk/Git/Prive/NukeTest/build

[...]

═══════════════════════════════════════
Target             Status      Duration
───────────────────────────────────────
Failure            Failed        &lt; 1sec
───────────────────────────────────────
Total                            &lt; 1sec
═══════════════════════════════════════

Build failed on 07/03/2021 14:10:17. (╯°□°）╯︵ ┻━┻
NotifyOnFailureAttribute: MSBUILD : error MSB1009: Project file does not exist.
ProcessException: Process 'dotnet' exited with code 1.
   &gt; /usr/local/share/dotnet/dotnet build DoesNotExist.csproj --verbosity Minimal
   @ /Users/e.heemskerk/Git/Prive/NukeTest/build
</code></pre>
<p>As for <em>which</em> build the error occurred in, the available information differs between build servers. We use TeamCity, so we can get the server URL and the build ID from NUKE’s built-in <a href="https://nuke.build/api/Nuke.Common/Nuke.Common.CI.TeamCity.TeamCity.html"><code>TeamCity</code> class</a>, which allows us to construct a direct URL to the build log of the failed build. Different CI systems might expose different information, and there might be more information available in environment variables than what is exposed by NUKE.</p>
<h2 id="conclusion">Conclusion</h2>
<p>While it takes a little bit of work, you <em>can</em> get notifications directly from NUKE into Slack, which saves you from having to babysit your CI system each time you merge a change.</p>
<p>Here’s an actual notification (obviously edited):
<img src="/content/images/2021/07/slack-notification.png" alt="A failure notification in Slack" /></p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2021/08/2020-10-08%20Achtergrond%209-1.png" medium="image" /></item><item><title>Creating reusable build scripts with NUKE components</title><description>How can we take advantage of the latest C# features to create highly reusable build scripts with NUKE?</description><link>https://www.erikheemskerk.nl/nuke-build-components/</link><guid isPermaLink="false">6723148be34046a582805f17a5f849e6</guid><category>NUKE</category><category>Cake</category><category>C# 8.0</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Sat, 10 Jul 2021 09:15:00 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2021/07/2020-10-08%20Achtergrond%209-2.png" alt="Creating reusable build scripts with NUKE components" /><p>When your application landscape expands, doing CI/CD becomes a challenge. Applications need to be built, tested, and deployed, and then tested again. Typically, each application is a bit of a snowflake in some way; it’s built in a slightly different way, or some tests need to be excluded, or publishing is more involved than with others. Maintaining CI/CD pipelines becomes a chore, and problems that arise tend to eat up a lot of time.</p>
<p>In this situation, it can be beneficial to simplify things a bit. In our case (that case being Coolblue), almost all applications have a three-stage build pipeline:</p>
<ul>
<li><strong>Pull Request</strong><br />
This is executed each time a pull request is made or updated; it builds the application and runs unit and integration tests.</li>
<li><strong>Build Release</strong><br />
This is executed each time a change is merged (or pushed) to the main branch; it also builds the applications and runs unit and integration tests, but it also prepares the application to be deployed–this means packaging the application, uploading it to our deployment system, and associating some metadata with it.</li>
<li><strong>Release</strong><br />
This is executed after the previous step completes successfully, and deploys the application to one of the environments. If the environment is the last one before production, it also runs automated acceptance tests.</li>
</ul>
<p>For each stage of the pipeline, there’s only one step: execute a PowerShell script called <code>build.ps1</code>, which is always located in the root of the application’s repository, and pass it some strictly defined arguments. These argument always include the ‘target’, or ‘what to do’, and, depending on the stage, arguments like ‘which environment’ and so forth.</p>
<p>If an application needs to do something special, it can almost always be solved by just modifying this PowerShell script. The CI/CD pipelines become static and can be created at the click of a button.</p>
<h2 id="cake">Cake</h2>
<p>Of course, only the most simplistic applications can get away with a build script as simple as <code>dotnet publish</code> and <code>dotnet test</code>. Deploying an application already involves using external tooling to be able to communicate with the deployment system. In addition, our build scripts do things beside building and testing, like:</p>
<ul>
<li>Adding build metadata, like when an application was built, and from which Git commit.</li>
<li>Setting up secrets to be able to run integration tests against a live database, or to be able to send authenticated requests to a deployed application.</li>
<li>Notifying the team when the application fails to build, tests fail, or when a deployment fails.</li>
</ul>
<p>While this <em>can</em> be done in PowerShell, it’s not the easiest language to use, and not everyone is well versed in it.</p>
<p>Enter Cake.</p>
<p>For those not familiar with it, <a href="https://cakebuild.net">Cake</a> is a build automation system with a C#-based <abbr title="Domain-Specific Language">DSL</abbr>. It is based on the concept of <a href="https://cakebuild.net/docs/writing-builds/tasks/">tasks</a>, which are small units of work, like restoring packages, building, or testing, which can have dependencies on, or trigger, other tasks.</p>
<p>For example:</p>
<pre><code class="language-csharp">Task(&quot;PullRequest&quot;)
    .IsDependentOn(&quot;Build&quot;)
    .IsDependentOn(&quot;Test&quot;);

Task(&quot;Build&quot;)
    .Does(() =&gt;
          {
              DotNetCoreBuild(&quot;MyApplication.sln&quot;);
          });

Task(&quot;Test&quot;)
    .Does(() =&gt;
          {
              DotNetCoreTest(&quot;test/MyApplication.Tests/MyApplication.Tests.csproj&quot;);
          });

RunTarget(&quot;PullRequest&quot;);
</code></pre>
<p>This will run the <code>PullRequest</code> task, which is dependent on <code>Build</code> and <code>Test</code> (in that order), so it will run <code>dotnet build MyApplication.sln</code> and <code>dotnet test test/MyApplication.Tests/MyApplication.Tests.csproj</code>.</p>
<p>So where does <code>build.ps1</code> come in? Well, you can’t just run a Cake script; you need a runner. The <code>build.ps1</code> script’s job is to bootstrap this. It downloads the Cake runner from NuGet and invokes the runner, which will then compile and execute the Cake script.</p>
<h2 id="bad-cake">Bad Cake</h2>
<p>Cake is a very nice system, but there are some problems when using it at scale, as we do.</p>
<p>Because Cake is not ‘pure’ C#, but rather a C#-based <abbr title="Domain-Specific Language">DSL</abbr>, enhanced with some additional features, there’s no <em>good</em> editor support. Some editors, like Visual Studio Code, <a href="https://marketplace.visualstudio.com/items?itemName=cake-build.cake-vscode">support syntax highlighting</a>, but IntelliSense is limited to the default ‘hey I’ve seen this symbol before but it might not be appropriate in this context’ version<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>. Because of this, the feedback loop is very cumbersome: edit a file, run <code>build.ps1</code>, get a syntax error, fix it, rinse and repeat.</p>
<p>It does not help that Cake’s primary building block, the task, is ‘stringly typed’. Task names are always expressed as strings. That means that if you mistype the name of a task somewhere, you won’t know until you run the build. Navigating to a particular target is also notoriously cumbersome.</p>
<p>Cake’s <a href="https://cakebuild.net/api/Cake.Common.IO.Paths/ConvertableFilePath/">API for dealing with file system paths</a> is well-meant, but because it is not consistently applied, it frequently means you have to convert to and from strings, and between ‘convertable paths’ and ‘actual paths’. This is a major source of compilation errors, again because there’s no proper editor support.</p>
<p>Finally, one of the biggest issues is that reusing code is not made very easy. You can write NuGet packages to extend Cake, but as far as I’m aware those cannot define new tasks. You can also package one or more Cake scripts into a NuGet package, but this is again cumbersome (as you have to mess about with <code>.nuspec</code> files), and as far as I’m aware, this also doesn’t let you define tasks.</p>
<h2 id="nuke">NUKE</h2>
<p>All of these issues were encountered relatively recently when I had to make changes to a Cake script. The way Cake scripts have evolved within our organization is that some people have invested some time into creating a single set of Cake scripts that does almost everything we want. That set of scripts was then massively copied and pasted into other projects. Over time, some scripts gained new abilities, but because there was no proper way of sharing them back to the origin or all of its clones, each build script becomes a snowflake. The result is that the mental load of modifying Cake scripts is massive, because you need to account for a specific script’s peculiarities and limitations.</p>
<p>It was at that point I started searching for an alternative. I quickly ran into another build automation system that I’d heard of before, but never really used: <a href="https://nuke.build">NUKE</a>. <a href="https://www.continuousimprover.com">Dennis Doomen</a> (also known as ‘the guy who wrote <a href="https://fluentassertions.com">FluentAssertions</a>’) had written <a href="https://www.continuousimprover.com/2020/03/reasons-for-adopting-nuke.html">a very nice article outlining why he thought it was a great replacement</a>. He outlines more reasons, but NUKE specifically addresses all of the issues I was having with Cake.</p>
<ul>
<li>It is ‘normal’ C# in a normal project in your solution, which means you get the benefits of IntelliSense, or Smart Completion, or whatever your editor calls it.</li>
<li>Targets (the NUKE equivalent of Cake’s tasks) are regular C# symbols (either fields or properties), which means the compiler and your editor are aware of when you mistype a name and can point this out to you.</li>
<li>The file system and path API is excellent, although it still looks weird when you see the ‘division’ operator being overloaded to construct paths (<code>Root / &quot;src&quot; / &quot;MyApplication&quot; / &quot;MyApplication.csproj&quot;</code>).</li>
<li>You can reuse code by using NuGet packages, just like in regular projects.</li>
</ul>
<p>The Cake example from before looks like this in NUKE:</p>
<pre><code class="language-csharp">class Build: NukeBuild
{
  public static int Main() =&gt; Execute&lt;Build&gt;(x =&gt; x.PullRequest);

  [Solution]
  Solution Solution;

  Target PullRequest =&gt;
    _ =&gt; _.Triggers(Build)
          .Triggers(Test);

  Target Build =&gt;
    _ =&gt; _.Executes(
      () =&gt; DotNetTasks.DotNetBuild(o =&gt; o.SetProjectFile(Solution))
      )
    );

  Target Test =&gt;
    _ =&gt; _.Executes(
      () =&gt; DotNetTasks.DotNetTest(o =&gt; o.SetProjectFile(Solution))
    );
}
</code></pre>
<h2 id="reusability">Reusability</h2>
<p>So how does one reuse code in a NUKE build script? Let’s say we want to reuse a target, like publishing in Release mode for the <code>win10-x64</code> runtime while treating warnings as errors.</p>
<p>Since the build class needs to contain all the supported targets, composition is not an option. The only option left, then, is to introduce a base class.</p>
<pre><code class="language-csharp">abstract class MyBuild: NukeBuild
{
  Target Publish =&gt;
    _ =&gt; _.Executes(() =&gt; { /* publish code goes here */ });
}

class Build: MyBuild
{
  Target BuildRelease =&gt;
    _ =&gt; _.Triggers(Publish);
}
</code></pre>
<p>Because of inheritance, the <code>Build</code> class now exposes two targets: <code>Publish</code> and <code>BuildRelease</code>. Job done, right? Well, not so fast. What about optional features? For example, not all of our projects have automated acceptance tests. If every build exposed a target for running acceptance tests, you’d have no idea whether that target is there because of the base class or because it actually does something useful.</p>
<p>Of course, we could define a base class for ‘build <em>without</em> acceptance tests’ and ‘build <em>with</em> acceptance tests’, but that only works for the one feature. If you have multiple optional features that are not mutually exclusive, then you end up having to create many different subclasses for all the different combinations. If you have <em>n</em> number of optional features, the number of subclasses you have to create (and maintain) is <em>2<sup>n</sup></em>. For two features, that’s 4 classes; for three features, 8 classes, and for four features it adds up to 16 classes. You’ll also have a lot of code duplication, because you can’t inherit from anything (otherwise you’d end up with the same problem you were trying to solve). You can see why this isn’t a great idea.</p>
<h2 id="default-interface-implementations">Default interface implementations</h2>
<p>Fortunately, NUKE’s got you covered. The solution is... <em>(drum roll)</em> multiple inheritance! Great, but C# doesn’t have multiple inheritance. Well, since C# 8.0, it kind of does.</p>
<p>C# has technically always supported multiple inheritance, using interfaces. Originally, this meant that you could only ‘inherit’ multiple contracts, but not actual behavior. That’s where <a href="https://www.erikheemskerk.nl/c-sharp-7-2-and-8-0-uncertainty-awesomeness/#default-interface-implementations-c-8.0">default interface implementations</a> come in. Introduced in C# 8.0, it allows any interface member to have a default implementation. An implementing class (or struct) does not need to provide an implementation for that member, although it is allowed to, in order to override the default behavior.</p>
<p>One of the examples of this I like, which was also used in a <a href="https://youtu.be/VdC0aoa7ung?t=715">video where Mads Torgersen explains C# 8.0’s new features</a>, was that of a logger. A typical logger interface has members like <code>LogDebug</code>, <code>LogInformation</code>, <code>LogError</code>, and so forth. It’ll usually also have something like a <code>Log</code> or <code>Write</code> method, which has the same parameters, preceded by a ‘level’ parameter. The implementation of <code>LogDebug</code> and similar methods is usually to call that <code>Log</code> method with the respective log level.</p>
<pre><code class="language-csharp">enum LogLevel
{
  Debug,
  Information,
  Warning,
  // etc.
}

interface ILog
{
  void Log(LogLevel level, string message);
  void LogDebug(string message);
  void LogInformation(string message);
  // etc.
}

class Log: ILog
{
  public void Log(LogLevel level, string message) { /* omitted for brevity */ }

  public void LogDebug(string message) =&gt; Log(LogLevel.Debug, message);
}
</code></pre>
<p><em>Every</em> implementation of <code>ILog</code> needs to implement <code>LogDebug</code> and friends, even though that implementation will likely be exactly the same as the existing ones.</p>
<p>With default interface implementations, the default implementation of these methods can be provided in the interface:</p>
<pre><code class="language-csharp">interface ILog
{
  void Log(LogLevel level, string message);
  void LogDebug(string message) =&gt; Log(LogLevel.Debug, message);
  void LogInformation(string message) =&gt; Log(LogLevel.Information, message);
  // etc.
}
</code></pre>
<p>Now the only thing an implementation of <code>ILog</code> <strong>needs</strong> to implement is the <code>Log</code> method. This also means that you can add new methods to the interface without breaking existing implementations.</p>
<h2 id="nuke-build-components">NUKE build components</h2>
<p>So how does all of this help us with the problem of supporting optional features in our NUKE build script? NUKE calls this concept ‘<a href="https://nuke.build/docs/sharing-builds/build-components.html">build components</a>’. It ‘allow us to organize parameters and targets in reusable build components, and freely compose our build out of them’.</p>
<p>In a nutshell, your <code>Build</code> class can ‘inherit’ from multiple interfaces, where each interface represents a ‘component’, which can have its own targets and parameters. Each interface is ultimately derived from a well-known interface (<code>INukeBuild</code>). NUKE understands this mechanism of defining targets, and will find targets defined on all ‘inherited’ interfaces.</p>
<p>Our earlier example with acceptance tests can now look like this:</p>
<pre><code class="language-csharp">interface IDefaultBuild: INukeBuild
{
  Target Publish =&gt; _ =&gt; _.Executes(() =&gt; /* publish code goes here */);
}

interface IBuildWithAcceptanceTests: INukeBuild
{
  Target RunAcceptanceTests =&gt; _ =&gt; _.Executes(() =&gt; /* code to run acceptance tests goes here */);
}

class Build: NukeBuild, IDefaultBuild, IBuildWithAcceptanceTests
{
  public static int Main() =&gt; Execute&lt;Build&gt;();
}
</code></pre>
<p>By just implementing the <code>IBuildWithAcceptanceTests</code> interface, the build gets a whole new set of features, whereas if you choose to not implement it, the features are not in your way.</p>
<p>But it gets better. In our pipeline, we run the acceptance tests as part of the <code>Release</code> target — first we release the application into an environment, and then we run acceptance tests. How do we combine this with the fact that acceptance tests are optional?</p>
<p>We can’t use <code>Triggers</code> on the <code>Release</code> target, because that would fail if the acceptance test component hasn’t been implemented. We <em>could</em> use <code>TriggeredBy</code> from the acceptance test target, but that will fail if the release component hasn’t been implemented. We don’t want to <em>force</em> people to implement particular components; they’re there if they are useful to you, and nothing more.</p>
<p>Instead, we can use <code>TryTriggeredBy&lt;T&gt;</code>. This lets you express ‘if the build is a <code>T</code>, this target is triggered by these targets’. This is just what we need. There are similar methods for <code>DependsOn</code>, <code>DependentOn</code>, <code>Triggers</code>, and even <code>After</code> and <code>Before</code>.</p>
<pre><code class="language-csharp">interface IDefaultBuild: INukeBuild
{
  Target Release =&gt; _ =&gt; _.Executes(() =&gt; /* omitted for brevity */);
}

interface IBuildWithAcceptanceTests: INukeBuild
{
  Target RunAcceptanceTests =&gt;
    _ =&gt; _.TryTriggeredBy&lt;IDefaultBuild&gt;(b =&gt; b.Release)
          .Executes(() =&gt; /* omitted for brevity */);
}
</code></pre>
<p>Now, if your build implements <code>IDefaultBuild</code> <em>and</em> <code>IBuildWithAcceptanceTests</code>, the <code>RunAcceptanceTests</code> will automatically be triggered by the <code>Release</code> target. I call that pretty sweet.</p>
<p>Combining all of this with the excellent value-binding mechanism and all the other benefits provided by NUKE has made me switch to NUKE for good.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>Unless you install <a href="https://github.com/cake-build/bakery">Cake.Bakery</a>, and how you should do that depends on how you’re running your Cake script. Even then, it’s fairly limited.<a href="#fnref:1" class="footnote-back-ref">&#8617;</a></p>
</li>
</ol>
</div>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2021/07/2020-10-08%20Achtergrond%209-2.png" medium="image" /></item><item><title>Event Feeds: Simple and reliable messaging infrastructure</title><description>Event feeds are a simple mechanism to enable applications to react to things happening in other applications, reliably, consistently, and quickly.</description><link>https://www.erikheemskerk.nl/event-feeds/</link><guid isPermaLink="false">605618814fc9bc45be1c9a7a</guid><category>Event Feeds</category><category>Microservices</category><category>Event-Driven Architecture</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Fri, 09 Oct 2020 10:26:15 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2020/10/2020-10-08-Achtergrond-9-2.png" alt="Event Feeds: Simple and reliable messaging infrastructure" /><p>How do you make an application take action when something happens in another application? How do you make sure this happens reliably and in a timely fashion? In a world of service-oriented architectures and microservices, this is a hugely underestimated and overlooked problem, in my experience.</p>
<p>In this article, I’ll try and explain a mechanism that I’ve been working with for a while now, which I think is the <em>simplest</em> mechanism to reliably make applications react to one another, and which is also easily maintainable.</p>
<p>First I’ll describe the mechanisms typically used to make applications react to one another, and illustrate where they exhibit problems. To make my examples a bit more relatable, including the ramifications of when the system fails, let’s describe a fictional application: part of an e-commerce back-office. This will be an incredible over-simplification of how things work in the real world, but it does the job.</p>
<p>There are three applications:</p>
<ul>
<li><strong>Ordering</strong>; this takes orders, validates them, and when they have been paid for, releases them to be fulfilled.</li>
<li><strong>Payments</strong>; this accepts and records payments from customers.</li>
<li><strong>Logistics</strong>; this makes sure products get picked, packed, and put in a box with a shipping label on it.</li>
</ul>
<p>Obviously there is some information exchange happening between these applications. Ordering will need to know when a payment has been made, so it can mark orders as ‘paid for’. Logistics will need to know when an order has been paid for, so it can direct workers to pick and pack the products.</p>
<h2 id="the-payment-transaction">The payment transaction</h2>
<p>We’ll focus on the ‘payment’ transaction. After a payment has been recorded in Payments, Ordering needs to be notified so it can evaluate whether the amount due for the order has been fully paid, after which it can ‘release’ the order to Logistics.</p>
<p>So how do we get this notification across?</p>
<h2 id="rpc">RPC</h2>
<p>The <em>easy</em> approach is to use an RPC, or Remote Procedure Call. There are many ways of doing this; you could use a TCP socket directly, or something a little more abstract, like SOAP or gRPC, or even something as simple as an HTTP request.</p>
<p>This is very easy to do; after a payment has been made and has been registered, Payments simply makes a request directly to Ordering, and that is that. It is done synchronously, before returning to the caller, which is likely a customer from a browser, or indirectly so, with the front-end making a call to Payments.</p>
<p>So what’s wrong with this? There are several problems. One of them is a high degree of coupling. Payments needs to know that Ordering is interested in this notification, and it also needs to know how it likes to receive this notification—the protocol—and how it can reach the other application. This is the kind of coupling that can slow down development; when the team maintaining Ordering wants to change some of the details, they will need to align with the team maintaining Payments, and they might need a multiple-step deployment to ensure nothing breaks. This is at odds with what is typically seen as the value of having microservices: being able to independently deploy changes.</p>
<p>Another problem is the need for availability. When Payments cannot reach Ordering, or an error occurs while handling the notification, what do we do then? Do we drop the notification? Do we wait and try again? We can retry, but only for some time; remember, the customer is sitting there, waiting. After a while, you have to give up. How do you handle that scenario?</p>
<p>If you want to notify multiple applications using RPC, pretty much the only option is to make multiple calls, which increases the coupling even further. Aside from that, how do you deal with failure? If you need to notify three separate applications, and notifying one of them fails, what do you do?</p>
<h2 id="message-queues">Message queues</h2>
<p>One solution that alleviates these problems to some extent is to use a <a href="https://en.wikipedia.org/wiki/Message_queue">message queue</a>. Message queues are difficult to get right, so they are usually provided in the form of an external service. This could be in the form of dedicated software, like RabbitMQ, or a service of your cloud provider, like AWS SQS and Azure Queue Storage, or even part of your database system, like SQL Server’s Service Broker or Oracle Advanced Queues.</p>
<p>Message queues have a well-known, fixed, API, so the only things teams will need to align on are the format of the message and the location of the queue. Queues are usually implemented in a way that makes them highly available, and because they are usually considered ‘infrastructure’, developers usually don’t have to worry a lot about keeping these services alive.</p>
<p>As a bonus, most queue brokers support the ‘publish/subscribe’ or ‘fan-out’ pattern, whereby a single notification is sent to multiple consumers. The publishing application only knows that it is sending a notification to a queue or a ‘topic’, nothing more. The consumers are responsible for subscribing to topics based on what they are interested in.</p>
<p>Note that the problems aren’t solved completely. They are simply less severe. A new problem is that both applications are now forced to use this service. This kind of coupling can make it difficult to make changes in your hosting infrastructure.</p>
<p>In essence, using this kind of specialized infrastructure between applications is like having multiple applications sharing the same database. There are all sorts of problems with that, like making it much more difficult to make changes, creating a single point of failure, limiting scalability, and making managing the infrastructure more complicated.</p>
<p>Depending on the specific queue broker you use, you might need to consider what happens if the queue broker itself experiences an outage while messages are ‘in flight’.</p>
<h2 id="atomicity-and-timing">Atomicity and timing</h2>
<p>Both RPC and message queues<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a> suffer from a different kind of problem; that of atomicity and timing. Consider this, pretty typical, zoomed out view of what a ‘transaction’ looks like.</p>
<pre><code class="language-csharp">var transaction = BeginTransaction();
UpdateDatabase();
transaction.Commit();

// here be dragons

SendNotifications();
</code></pre>
<p>Note the line that says <code>// here be dragons</code>. The work in the database has been performed in a nice transaction, so either all of it has been performed—or, if something goes belly-up, nothing. Sending the notification, however, is not protected by anything. If an error occurs during or before sending the notification, you’re screwed; your database transaction has already been committed. Database operations can usually be reverted, but that is not always easy or desirable.</p>
<p>What if your application dies at that point in time?  A fatal error happens, one of the cleaners unplugs the server, or the virtual machine dies. Your transaction has already been committed, but nobody knows about it. Applied to our example, this means the payment has been registered, but Ordering never hears about it, and therefore never releases the order to be fulfilled. That customer is not going to be a happy camper.</p>
<p>Well, we just do the notification inside the transaction, right? Unfortunately, that doesn’t magically make the notification transactional.</p>
<pre><code class="language-csharp">var transaction = BeginTransaction();
UpdateDatabase();
SendNotifications();

// here be dragons

transaction.Commit();
</code></pre>
<p>The point of dragons is still in the same place, but it’s now <em>after</em> the notification instead of before it. Why are there still dragons? Consider what happens if your application dies at that point. You’ve already sent a notification to other applications that something has happened, but the database transaction will be rolled back. In reality, nothing has happened, so you’re effectively lying to other applications.</p>
<p>I also mentioned that timing was a problem, and it surfaces in this order of operations. You’re telling other applications ‘this has happened’, but you haven’t committed that information to the database yet. What if the application you’ve just notified very quickly turns around and asks you for more information? Especially if the notification is synchronous, that might even be before the notification completes.</p>
<p>Payments might be telling Ordering ‘a payment has been made’, upon which Ordering asks ‘oh really, so how much has been paid in total?’. Because the original transaction has not yet been committed, the answer might be ‘nothing’. And yet again, we have effectively lost information and gained an unhappy customer.</p>
<h2 id="storing-notifications">Storing notifications</h2>
<p>Because of these atomicity and timing problems, I consider sending notifications on the spot, without any back-ups, to be ‘unreliable’; there is a very real possibility a notification is lost because of a temporary disruption, like a network glitch or a VM dying.</p>
<p>We can make sending notifications ‘reliable’ again by making it asynchronous. Instead of immediately sending notifications, we store them somewhere, to be picked up by a background process. This is called the Outbox Pattern. If we store the notifications in a database, we can make it part of the database transaction, so either the intended changes happen <em>and</em> the notification is stored to be sent, or neither of them happen.</p>
<p>This approach has the added benefit of being resilient to temporary outages. The background process picks up the next pending notification, sends it, and then marks it as ‘done’, which can either be deleting it, or setting a flag. Rinse and repeat. If, for some reason, the notification cannot be sent, because the receiving party is not reachable or not responding, the background process backs off and tries again.</p>
<p>Again, this does not completely solve the atomicity problem, it changes it. Instead of not sending notifications, you might be sending them more than once. You’re still dealing with operations in two different systems—your database and another application, whether that’s an actual application or a piece of infrastructure like a queue. This can, and should, be solved by using notifications that are <em>idempotent</em>, which I’ll describe in a bit.</p>
<h3 id="on-idempotent-messages">On idempotent messages</h3>
<p>A brief interlude about what <em>idempotent</em> means. It derives from the Latin words <em>idem</em> (meaning ‘same’) and <em>potent</em> (meaning ‘having power’), so ‘having the same power’. It means that an operation, when performed more than once, will have the same outcome as the first time. Fine examples of this are arithmetic operations; 2 + 2 will always equal 4, no matter how often you compute it. The polar opposite would be getting a random number; that is intended to produce a different value each time.</p>
<p>How does this apply to messages? The go-to example to illustrate idempotent and non-idempotent messages is when a quantity of something has changed, like the quantity you have on stock of a particular product.</p>
<p>Consider the following two messages, describing that the stock quantity of a product has increased.</p>
<pre><code class="language-csharp">class ProductStockQuantityIncreased
{
  public string Sku { get; }
  public int QuantityAdded { get; }
}
</code></pre>
<p>… versus…</p>
<pre><code class="language-csharp">class ProductStockQuantityIncreased
{
  public string Sku { get; }
  public int QuantityAdded { get; }
  public int StockQuantity { get; }
}
</code></pre>
<p>In the first example, if you start with a quantity of zero, and you have a single message saying <strong>5</strong> have been added, which you receive <strong>twice</strong>, it looks like you have a total quantity of <strong>10</strong>.</p>
<p>On the other hand, in the second example, you have a message saying <strong>5</strong> have been added and the new quantity is <strong>5</strong>. If you receive that twice, you’ll still know the correct total quantity.</p>
<p>If you really want to avoid receiving a message twice, you could attach a unique ID to each message, and keeping track of which message you have already received. This comes with its own special set of problems, though, especially since RPC and most of the queueing mechanisms I talked about allow messages to be received out of order; e.g. message A, then message B, then a retry of message A. The mechanism through which you receive the message also doesn’t use the same transaction as where you keep track of which messages you’ve received<a id="fnref:2" href="#fn:1" class="footnote-ref"><sup>1</sup></a>, so you have another atomicity problem, but this time on the receiving end.</p>
<p>Making messages idempotent doesn’t solve any of the coupling problems attached to the RPC or message queue solution, though.</p>
<h2 id="pulling-notifications-or-events">Pulling notifications (or events)</h2>
<p>Let’s make a small change. We still store notifications, instead of sending them on the spot, but instead of <em>pushing</em> them to other applications, we have those applications <em>pull</em> the notifications from us. The notifications describe what happened, so they’re less like notifications, and more like events, which is the terminology I’ll use from now on. We create an endpoint that provides the caller a list of events it needs to process, and it is then up to that application to keep track of which ones have already been processed.</p>
<p>To be clear, this means Payments exposes an endpoint with a list of events to other applications. Ordering regularly polls this endpoint to retrieve any new events. In other words, Payments <em>produces</em> a list of events, which Ordering <em>consumes</em>.</p>
<p>How does this solve the problems we’ve seen?</p>
<p>Our first problem, coupling, is still there, but it’s been inverted; instead of Payments being dependent on Ordering, it’s actually Ordering which is dependent on Payments. Depending on how you implement the list of events, the coupling can also be very minimal; down to a URL and knowledge of the structure and format of events. What’s more, if you have more than one application that needs to be notified, this doesn’t increase the coupling in any single application<a id="fnref:3" href="#fn:2" class="footnote-ref"><sup>2</sup></a>.</p>
<p>The need for availability has also pretty much vanished. When Ordering can’t reach Payments to retrieve the list of events, that isn’t really a problem. Sure, orders will not be released to be fulfilled, but no notifications are lost. As soon as connectivity is restored, Ordering will pick up where it left off, and business will continue. If Ordering cannot process an event for whatever reason—maybe it cannot reach its own database—it just gives up and retries after waiting a while. The event is still there, no matter how many times you retry. This also means the process of handling events is <em>self-recovering</em>.</p>
<p>We don’t need any specific infrastructure for this solution; it involves applications communicating with other applications. The only ‘infrastructure’ we need is HTTP, and that is mature, ubiquitous, and flexible enough that it shouldn’t cause you any headaches when you make changes to your hosting infrastructure (like moving to a different cloud provider).</p>
<p>When Payments stores events in the same database transaction it uses to do the actual work, you’ve solved the atomicity problem. Because a notification only appears in the list of events provided by the endpoint after the database transaction has been committed, the timing problem has also been dealt with.</p>
<p>An additional advantage of the fact that events remain available is that you can re-process events. Let’s say Ordering maintains a private cache of how much has been paid in total for each order, by accumulating data from the events it pulls from Payments. Unfortunately, instead of <em>adding</em> amounts, it <em>subtracts</em> them. Oops. No problem; just fix the issue in code and have the application re-process all events. It can also be a tremendous advantage when implementing new functionality that uses data from the events you didn’t need previously.</p>
<h2 id="kafka">Kafka?</h2>
<p>Some of you might be thinking ‘this is very similar to a Kafka broker,’ and you’d be right, up to a certain point. <a href="https://kafka.apache.org/">Apache Kafka</a> is a ‘distributed event streaming platform’ that uses a lot of the same ideas (an append-only list of events, clients pulling data and keeping track of where they are), but then in a distributed fashion. A lot of the smarts in Kafka is in the way they manage clusters of servers and making sure the members of a cluster agree with one another.</p>
<p>Kafka is, like a message queue, a stand-alone piece of infrastructure, which means it has the same ‘problems’; no atomicity<a id="fnref:4" href="#fn:3" class="footnote-ref"><sup>3</sup></a>, and it forces both producer and consumer to use the same infrastructure. It is very well suited if you have to distribute <em>a lot</em> of data, if you have <em>a lot</em> of consumers, or if there are <em>a lot</em> of consumers. For smaller scale problems, it feels like shooting at a mosquito with a cannon.</p>
<h2 id="protocol">Protocol</h2>
<p>Now that we’ve established on a high level what the mechanism will be, let’s talk about the actual implementation of this list of events. We could, of course, just <em>roll our own</em> protocol. How hard could it be? It’s just a list of events. Of course, this list of events will grow larger and larger, so we’ll probably need some pagination. How does a consumer know what the URL for a given page is? What about the format of the list itself? If we don’t establish one, each team will invent their own little format, which means a consuming application will need a new implementation for each event feed they consume.</p>
<p>Instead, let’s pick a standardized protocol and format. We’ll have less thinking to do, and it’s very likely a client library already exists for your platform of choice. We need something that is well-suited for large collections of items and for figuring out ‘what has changed’. It should also support some mechanism indicating the different kinds of events that are stored.</p>
<h2 id="enter-atom">Enter Atom</h2>
<p>It turns out there’s a format that suits these requirements very nicely. It’s called <a href="https://tools.ietf.org/html/rfc4287">Atom</a>, and it’s most frequently used as a syndication mechanism for websites that have frequently changing content, like news sites. It supports <a href="https://tools.ietf.org/html/rfc5005">pagination</a> and different kinds of content (through media types). It’s XML-based and looks a little something like this:</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;feed xmlns=&quot;http://www.w3.org/2005/Atom&quot;&gt;
  &lt;id&gt;urn:publicid:PaymentHandlingEvents&lt;/id&gt;
  &lt;updated&gt;2020-01-09T18:02:13.242Z&lt;/updated&gt;
  &lt;link rel=&quot;self&quot; href=&quot;https://payments.myshop.eu/events/mostRecent&quot; /&gt;
  &lt;link rel=&quot;prev-archive&quot; href=&quot;https://payments.myshop.eu/events/2020-01-08T0000&quot; /&gt;
  &lt;entry&gt;
    &lt;id&gt;urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a&lt;/id&gt;
    &lt;updated&gt;2020-01-09T18:02:13.242Z&lt;/updated&gt;
    &lt;content type=&quot;application/vnd.myshop.payments.paid+json&quot;&gt;
eyJQYXltZW50VHJhbnNhY3Rpb25JZCI6Mzk4MDg3MjM0Nzk4OTIsIkFtb3VudCI6MjI0LjUsIkN1cnJlbmN5IjoiRVVSIiwiUmVmZXJlbmNlIjoiMjM5ODcyOSJ9
&lt;!-- {&quot;PaymentTransactionId&quot;:39808723479892,&quot;Amount&quot;:224.5,&quot;Currency&quot;:&quot;EUR&quot;,&quot;Reference&quot;:&quot;2398729&quot;} --&gt;
    &lt;/content&gt;
  &lt;/entry&gt;
  &lt;!-- more entries go here --&gt;
&lt;/feed&gt;
</code></pre>
<p>There’s a bit of ceremony in the first few lines of the XML, which are not all that interesting—yet—but let’s look at the <code>entry</code> tag. When using Atom as an event feed, each event will be represented by an <code>entry</code> element.</p>
<p>The <code>entry</code> element has an <code>id</code> element, containing an <a href="https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier">IRI</a>, which in this case is a <a href="https://en.wikipedia.org/wiki/Uniform_Resource_Name">URN</a> containing a <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier">UUID</a>. It doesn’t have to be a UUID, per se, but it should be something that uniquely identifies the event, because this is what a consumer will use to keep track of what they’ve already seen.</p>
<p>The <code>content</code> element is where the actual payload of the event is. In order for consumers to be able to tell which kind of event this is—as there could be multiple kinds of events in a single feed—we use the <code>type</code> attribute and give the event a proper <a href="https://en.wikipedia.org/wiki/Media_type">media type</a>. Because the standard <a href="https://tools.ietf.org/html/rfc4287#section-4.1.3.3">says</a> any content whose media type does not start with <code>text/</code> should be Base64-encoded, that’s what you see here. The giant wall of text starting with <code>eyJQY</code> is the Base64-encoded version of the JSON in the comment below it.</p>
<p>This example only contains a single event, but if there were more, they would be listed in reverse chronological order. That is, it starts with the most recent event, and ends with the oldest event.</p>
<h2 id="pagination-and-navigation">Pagination and navigation</h2>
<p>As I mentioned earlier, we need support for pagination, because this list of events will likely become <strong>huge</strong>. So how does pagination work in a feed like this? Given that this is the entry page of the feed, how does a consumer know how to get to the next page?</p>
<p>‘Next page’ is a very confusing name, by the way, because that depends on which way you’re navigating. Given our context, you always start at the entry page, with the most recent events in reverse chronological order. If you want something that’s older than the last event on the entry page, you logically want the item on the ‘next’ page.</p>
<p>Back to our original question: traditionally, the answer would be that you establish a ‘contract’ of sorts; if the URL of the entry page is something like <code>/events/mostRecent</code>, the next page could be at <code>/events/1</code>, followed by <code>/events/2</code>, and so on.</p>
<p>This works well, but because the consumer now needs to know about this contract, the familiar spectre of <em>coupling</em> rears its ugly head. Besides that, what if ‘page 1’ isn’t the next page from the entry page, but instead it’s the page with the events from the beginning of time? What if pagination isn’t based on the number of events stored, but on date and time? The document doesn’t contain any data that can be used to generate these kinds of URLs from.</p>
<p>Instead, the feed uses <em>hyperdata</em> to link to other pages, much like a normal web page would. When you google something, you don’t manually change the URL in the address bar to go to the next page; you click the ‘Next’ link at the bottom.</p>
<p>The document contains the following element:</p>
<pre><code class="language-xml">&lt;link rel=&quot;prev-archive&quot; href=&quot;https://payments.myshop.eu/events/2020-01-08T0000&quot; /&gt;
</code></pre>
<p>The <code>rel</code> attribute indicates what kind of relationship this link has to this document (in this case, the ‘<a href="https://tools.ietf.org/html/rfc5005#section-4">previous page of archived data</a>’), and the <code>href</code> element is simply an opaque URL that the consumer should follow to get to the next page. This way, the consumer only needs to know that the next page is indicated by a <code>link</code> element with a <code>rel</code> attribute equal to <code>prev-archive</code>, and the producer of the feed is free to use whatever pagination strategy they like.</p>
<p>But why is <code>prev-archive</code> an appropriate relationship? Why not <code>prev</code>? Or <code>next</code>, for that matter? From the point of view of the standard for Feed Paging and Navigation, <a href="https://tools.ietf.org/html/rfc5005#section-4">RFC 5005</a>, ‘previous’ is actually the term used to describe ‘older’ pages. The difference between <code>prev</code> and <code>prev-archive</code> is that <code>prev-archive</code> indicates that all the pages of the feed together form a complete set of entries, and that:</p>
<blockquote>
<p>Archive documents are feed documents that contain less recent entries in the feed.  The set of entries contained in an archive document published at a particular URI SHOULD NOT change over time.  Likewise, the URI for a particular archive document SHOULD NOT change over time.</p>
</blockquote>
<p>In other words, <code>prev-archive</code> describes the stability of the pages that are being linked to. Because ‘archive’ pages never change, this means you can have them be cached essentially <em>forever</em>. This becomes interesting when feeds are being used at scale.</p>
<h2 id="client-processing">Client processing</h2>
<p>So how do you process this feed of events as a client application?</p>
<p>When you’re working with a queue, you dequeue an item from the queue, you process it, and after you acknowledge it’s been processed successfully, it’s <em>gone</em>. In other words, processing items <em>changes</em> the queue; the queue is stateful. This is not the case with the event feed; the events stay there after you process them. How do you know which event to process next? It’s like reading a book; you start with the first page, and after a while you put it down. You remember where you left off by using a <strong>bookmark</strong>.</p>
<p>Of course, that is where the analogy ends, because a bookmark is usually something physical that you insert in between pages of a book, but that is not something you can do with this event feed. Instead, think of it as writing down on a piece of paper which page you read last. A more accurate analogy would be that each paragraph in the book has a unique number, and you write down on a piece of paper at which paragraph you left off.</p>
<p>You always start with the entry page—because that’s the only page whose URL you know. Then follow these steps:</p>
<ol>
<li>Try to find the entry whose <code>&lt;id&gt;</code> element matches your bookmark.</li>
<li>If you find it, then everything (including entire pages) that <strong>comes before it</strong> is new (remember, events appear in the feed in <em>reverse chronological order</em>).</li>
<li>If you don’t find it, navigate to the ‘next’ page, by finding a <code>&lt;link&gt;</code> element with the relationship <code>prev-archive</code>, read the page indicated by its <code>href</code>, and start from step 1.</li>
</ol>
<p>Once you have found out what the new events are, you process them as follows:</p>
<pre><code class="language-csharp">try
{
  string? bookmark = GetBookmark();
  var events = GetNewEventsSince(bookmark);

  foreach (var @event in events)
  {
    ProcessEvent(@event);
    UpdateBookmark(@event.Id);
  }
}
catch (Exception ex)
{
  // log exception, don't rethrow
}
</code></pre>
<p>In this high-level code, <code>@event.Id</code> would contain the value of the <code>&lt;id&gt;</code> element. Obviously <code>GetNewEventsSince</code> contains code to navigate and parse an Atom feed and some logic to correctly deserialize events from their media types, and <code>ProcessEvent</code> probably uses a DI container to invoke an event handler, but otherwise, just invoke this code on an interval—and you’re done.</p>
<p>Why the big <code>try</code>/<code>catch</code> around the entire block of code? As I mentioned, this code will be invoked on an interval, likely a timer, and we don’t want an unhandled exception to stop that timer.</p>
<p>If you make sure that the code invoked by <code>ProcessEvent</code> uses the same transaction as that of <code>StoreBookmark</code>, you can achieve something pretty nice: in-order <em>exactly-once</em> delivery. This means that the order of events is always exactly the same as how they were stored, and you will never process an event more than once.</p>
<p>Of course, that is kind of a white lie, because if processing an event throws an exception, it will not update the bookmark, but at the next interval, it will definitely process the same event again. The event is, in effect, only delivered <em>successfully</em> once, assuming it is eventually processed successfully.</p>
<p>There are, as always, a lot of gotchas and subtleties around how to make this work efficiently and correctly, but I will touch on those in a separate post (probably several, actually). I will touch on one which is probably brewing in the back of your mind: latency.</p>
<h3 id="latency">Latency</h3>
<p>In the very first paragraph I mentioned that one of the benefits was responding ‘in a timely manner.’ In its current form, this is limited to the interval at which you check for new events. What if your requirements for latency are very strict, like having responding within half a second? Obviously you can set the interval between checks to something very low, say 250 milliseconds, but this means the application hosting the event feed gets 4 requests per second, from just one application. That doesn’t scale very well.</p>
<p>There is a nicer approach, which scales a lot better, and which can get you latency in the order of tens of milliseconds. It involves using a backchannel to signal to clients that a new event is available. Clients then act upon that signal by checking for new events and processing them as usual. Think of it as a secondary trigger to check for new events, in addition to the timer firing on an interval.</p>
<p>It’s important that this backchannel only contains a notification that a new event is available, and preferrably its unique ID, but not the event itself. This is because the point of this backchannel is to be an optimization, and it can therefore be relatively unreliable. If you miss a single message, you will either pick it up, in the right order, when the next notification arrives, or when the timer fires.</p>
<p>This backchannel should be a push mechanism, so that clients can react as quickly as possible. It should also be client-initiated, by which I mean the client is the one initiating the request for real-time updates.</p>
<p>One of the options that meets this requirement is a fan-out (or ‘publish/subscribe’) queue. As I’ve said before, I don’t like to use this kind of specific (and stateful) infrastructure to communicate across application boundaries. Another option is to use a WebSocket. This lets a client connect to a well-defined URL to establish a bidirectional communication channel. <a href="https://dotnet.microsoft.com/apps/aspnet/signalr">SignalR</a> is a solid option in the .NET ecosystem, for both publishing and receiving the notifications.</p>
<h2 id="example">Example</h2>
<p>Let’s look at an example of the payment transaction as a high-level exchange between the systems involved, in this case Ordering and Payments. I’m using numbers as unique identifiers for the events, for clarity, while you would probably use UUIDs in a real application.</p>
<p><strong>Situation</strong><br />
Payments has a single event in its feed; a payment for order number 1. The unique ID of this event is <code>1</code>. To make this sample more illustrative, it paginates based on the number of events, and its page size is 1, meaning each event gets its own page.</p>
<p>Ordering’s bookmark for the Payments event feed is event <code>1</code>.</p>
<p><strong>The transaction</strong></p>
<ol>
<li>Ordering fetches the entry page of the Payments event feed. The feed still only contains the one event, so there are no new events.</li>
<li>The customer makes a payment for order number 2, through Payments. Payments registers the payment and appends an event to its feed, with unique ID <code>2</code>.</li>
<li>Ordering begins its next check of the Payments event feed. Its entry page only contains event <code>2</code>. Since it doesn’t contain the bookmark event (<code>1</code>), Ordering requests the next page of events.</li>
<li>Ordering finds its bookmark event on the next page, meaning everything that came after it (chronologically) is new. In this case that is only event <code>2</code>.</li>
<li>Ordering processes the event and marks order number 2 as ‘paid for’. It appends an event to its own event feed, to be picked up by other interested parties, like Logistics.</li>
<li>Ordering updates its bookmark for the Payments event feed to event <code>2</code>.</li>
</ol>
<h2 id="sample-application">Sample application</h2>
<p>All of this so far has been a bit theoretical and high-level, so I will leave you with a small sample application that not only demonstrates a very simplistic event feed, but also shows off the notification backchannel. It should be a good basis for you to start playing around with event feeds yourself.</p>
<p><strong><a href="https://github.com/heemskerkerik/EventFeedDemo">heemskerkerik/EventFeedDemo</a></strong></p>
<p>In future posts, I will discuss error handling, reliably storing events, and scaling out, amongst others.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>Except for when the queue is a database queue in the same database your application does its regular work in.<a href="#fnref:1" class="footnote-back-ref">&#8617;</a><a href="#fnref:2" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>This is also the case for ‘publish/subscribe’ queues, though.<a href="#fnref:3" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:3">
<p>Kafka does have ‘idempotent delivery’, but that doesn’t help you if your application dies before delivering the message.<a href="#fnref:4" class="footnote-back-ref">&#8617;</a></p>
</li>
</ol>
</div>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2020/10/2020-10-08-Achtergrond-9-2.png" medium="image" /></item><item><title>Revisited: Is WCF faster than ASP.NET Core? Of course not! Or is it?</title><description>Revisiting an article about how I got triggered after somebody exclaimed that WCF had lower response times than ASP.NET Web API and ASP.NET Core MVC.</description><link>https://www.erikheemskerk.nl/revisited-benchmark-wcf-webapi-aspnetcore-mvc/</link><guid isPermaLink="false">605618814fc9bc45be1c9a78</guid><category>Benchmarking</category><category>.NET Core</category><category>MessagePack</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Thu, 05 Apr 2018 07:30:14 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2018/04/Achtergond-8.2@2x-1.png" alt="Revisited: Is WCF faster than ASP.NET Core? Of course not! Or is it?" /><p>Previously, I wrote an article about how I got triggered after somebody on Reddit exclaimed that <a href="https://www.erikheemskerk.nl/benchmark-wcf-webapi-aspnetcore-mvc/">WCF <del>was faster</del> had lower response times than ASP.NET Web API and ASP.NET Core MVC</a>.</p>
<p>The outcome was that, yes, with the default configuration, WCF does have lower response times. When you start tweaking with the serializers, both ASP.NET Web API and ASP.NET Core MVC come out on top.</p>
<p>The article kicked up a bit of a storm on Reddit, though, with people complaining that the comparison wasn’t fair, that I should have done this or that. Somebody even wrote an entire blog post about how it <em>should</em> be done. I wanted to address some of those comments.</p>
<h2 id="expectations">Expectations</h2>
<p>In my original article, I did not explain my intentions very well. It was not meant as a be-all-end-all determination of whether to use WCF or not. There are very valid reasons for using or not using WCF, besides the performance. I was simply trying to figure out whether WCF could have better per-request latency than the existing ASP.NET offerings.</p>
<h2 id="latency-doesnt-mean-better-performance">Latency doesn’t mean better performance</h2>
<p>Several people pointed out that performance isn’t just about latency. Specifically ASP.NET Core and Kestrel have been designed to be able to handle many requests concurrently, probably at the cost of some latency. I definitely agree with the statement, but my post was only about latency, because it is still an important metric in the performance of your service. It also happens to be one of the easier ones to measure.</p>
<h2 id="youre-doing-unnecessary-in-place-computations">You’re doing unnecessary in-place computations</h2>
<p>Josh Bartley wrote an <a href="https://joshbartley.com/response-is-wcf-faster-than-asp-net-core/">entire article</a> in reply to mine. Thanks for taking the time to do that, Josh.</p>
<p>In the article, he mentions that I’m doing reflection in every call to construct the URI for the Web API/ASP.NET Core requests. He makes a fair point, along with some other ones, like how I was using delegates to switch between implementations. I’m not sure how much that specifically affects the numbers, but it sure is ugly. I rewrote all of the benchmarks to use a type hierarchy instead of big ugly switch statements, and I made sure to only use reflection once, during the initialization. Let’s see what has changed.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeAspNetCoreMessagePackFuncs</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 017,7 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">8 882,4 μs</td>
</tr>
</tbody>
</table>
<p>There’s a very small improvement, but it’s not very impressive. I think it’s safe to say that reflecion and using delegates was not really an issue.</p>
<h2 id="asynchronous-is-killing-the-numbers">Asynchronous is killing the numbers</h2>
<p>Redditor <a href="https://reddit.com/u/Langebein">Langebein</a> remarked the following:</p>
<blockquote>
<p>You’re doing <code>PostAsync</code> in the WebApi tests, while doing synchronous calls with WCF. I’d wager a lot of the difference is made up from starting tasks and spinning up threads with the fancy <code>HttpClient</code>.</p>
</blockquote>
<p>That’s an interesting point. With my new code structure in place, I could easily add other types of clients. First, let’s see what happens if we don’t <code>await</code> everything.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">8 699,5 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">8 882,4 μs</td>
</tr>
</tbody>
</table>
<p>That’s a significant difference already. But Langebein was talking about <code>HttpClient</code> as a whole. So let’s see what happens when we use good ol’ <code>HttpWebRequest</code>.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeWebApiMessagePackHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 292,9 μs</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 372,5 μs</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">7 023,7 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">8 699,5 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">8 882,4 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 347,1 μs</td>
</tr>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 306,5 μs</td>
</tr>
</tbody>
</table>
<p>That’s a pretty big win. For all its fanciness, <code>HttpClient</code> is adding a lot of overhead, possibly related to its asynchronicity. For reference, I’ve added the fastest WCF result from the earlier post, and there is an undeniable advantage to ASP.NET Core and MessagePack. ASP.NET Web API does pretty well, too.</p>
<p>Since we’ve established that using <code>HttpWebRequest</code> offers the lowest latency, let’s establish a new baseline.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 306,5 μs</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 125,9 μs</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">12 535,9 μs</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">14 204,2 μs</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">7 023,7 μs</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">12 461,1 μs</td>
</tr>
<tr>
<td>LargeWebApiUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">11 023,6 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">18 784,1 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 347,1 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">20 484,6 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 944,2 μs</td>
</tr>
</tbody>
</table>
<p>Now we can see MessagePack and Utf8Json (on ASP.NET Core MVC) easily overtaking WCF, and even XML on ASP.NET Core MVC is getting close.</p>
<h2 id="what-about-binary-serialization-for-wcf-and-raw-tcp">What about binary serialization for WCF? And raw TCP?</h2>
<p>Several people commented that I should have included more WCF options, such as binary serialization and the revered <code>NetTcpBinding</code>. Binary serialization was simply something I forgot about after a couple of years of not doing WCF.</p>
<p><code>NetTcpBinding</code> is not something I think is fair to compare with. There is just an overhead to using HTTP that the ASP.NET Core and Web API tests cannot avoid. I decided to include it for argument’s sake.</p>
<p>Interestingly enough, the way I was invoking the WCF client meant I was creating a new client for each call. For the HTTP bindings, this was not an issue, because they’re handing off connection management to the HTTP library. <code>NetTcpBinding</code> has to do it by itself. The way I was doing it, I was never actually releasing clients, which meant sockets would stay open forever. I tried benchmarking it, but it never even got past the ‘pilot’ phase, where BenchmarkDotNet tries to determine the optimum number of invocations per run.</p>
<p>After I’d modified the code to create a client and immediately release it afterwards, things were a little better. A <em>little</em> better. Now I would actually reach the ‘main’ phase, but eventually, the process would either halt or throw an exception, because it was occupying sockets so quickly that they weren’t getting released in time.</p>
<p>Eventually I had to settle for using a single client instance for the lifetime of the benchmark. Unsurprisingly, the results are very good.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 306,5 μs</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 125,9 μs</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">12 535,9 μs</td>
</tr>
<tr>
<td><strong>LargeWcfNetTcp</strong></td>
<td style="text-align: right;">100</td>
<td style="text-align: right;"><strong>5 469,9 μs</strong></td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 347,1 μs</td>
</tr>
</tbody>
</table>
<p>Binary serialization <em>was</em> a fair option, that I’d just forgotten to include. It was quickly included, and it is definitely the fastest out-of-box serialization option for WCF. Still, MessagePack on ASP.NET Core MVC has an ever so slightly lower response time.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 306,5 μs</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 125,9 μs</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">12 535,9 μs</td>
</tr>
<tr>
<td><strong>LargeWcfBinary</strong></td>
<td style="text-align: right;">100</td>
<td style="text-align: right;"><strong>6 889,3 μs</strong></td>
</tr>
<tr>
<td>LargeWcfNetTcp</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">5 469,9 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 347,1 μs</td>
</tr>
</tbody>
</table>
<h2 id="how-about-a-comparison-between-full-framework-and.net-core">How about a comparison between Full Framework and .NET Core?</h2>
<p>I very much wanted to include this, but I kept running into issues with BenchmarkDotNet, which either failed half-way, or didn’t report all of the metrics.</p>
<h2 id="theres-no-fair-comparison-between-wcf-and-the-others">There’s no fair comparison between WCF and the others!</h2>
<p>Redditor <a href="https://reddit.com/u/IAmVerySmarter">IAmVerySmarter</a> (who, incidentally, also suggested using <code>NetTcpBinding</code>) complained that, because there was no benchmark between WCF and ASP.NET Core MVC or Web API using the MessagePack, it wasn’t fair to compare.</p>
<p>Unfortunately, the only package I could find to use MessagePack with WCF is <a href="https://github.com/schulz3000/msgpack.wcf">MsgPack.Wcf</a>, which uses <a href="https://github.com/msgpack/msgpack-cli">MsgPack.Cli</a> instead of <a href="https://github.com/neuecc/MessagePack-CSharp">MessagePack</a>. According to the <a href="https://github.com/neuecc/MessagePack-CSharp#messagepack-for-c-net-net-core-unity-xamarin">benchmarks</a>, MsgPack.Cli takes a lot more time to serialize and deserialize than MessagePack. It’s still a fair comparison, so let’s see.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 306,5 μs</td>
</tr>
<tr>
<td>LargeWcfBinary</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 889,3 μs</td>
</tr>
<tr>
<td><strong>LargeWcfMsgPackCli</strong></td>
<td style="text-align: right;">100</td>
<td style="text-align: right;"><strong>22 791,3 μs</strong></td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 347,1 μs</td>
</tr>
<tr>
<td><strong>LargeWebApiMsgPackCliHttpWebRequest</strong></td>
<td style="text-align: right;">100</td>
<td style="text-align: right;"><strong>28 510,5 μs</strong></td>
</tr>
<tr>
<td><strong>LargeAspNetCoreMsgPackCliHttpWebRequest</strong></td>
<td style="text-align: right;">100</td>
<td style="text-align: right;"><strong>26 311,1 μs</strong></td>
</tr>
</tbody>
</table>
<p>As you can see, MsgPack.Cli on either ASP.NET Core MVC or ASP.NET Web API takes several times longer than MessagePack. In fact, it takes longer than any other serializer tested. However, MsgPack.Cli on WCF takes a bit less time than on ASP.NET Core or ASP.NET Web API. This <em>might</em> indicate that the underlying infrastructure for WCF is the faster one.</p>
<h2 id="can-you-add-frameworkserializer-to-the-comparison">Can you add [framework/serializer] to the comparison?</h2>
<p>I <em>could</em>, yes. On the other hand, the source code is <a href="https://github.com/heemskerkerik/WcfVsBenchmark/tree/follow-up">right there</a>, so you could also fiddle around with it yourself.</p>
<h2 id="zeroformatter">ZeroFormatter</h2>
<p>One serializer that I’ve seen mentioned several times is <a href="https://github.com/neuecc/ZeroFormatter">ZeroFormatter</a>. This serializer claims to provide ‘infinitely fast deserialization’. That’s a bold claim, even from its creator, <a href="https://github.com/neuecc/">neuecc</a>, who’s also responsible for the excellently performing <a href="https://github.com/neuecc/MessagePack-CSharp">MessagePack</a> and <a href="https://github.com/neuecc/Utf8Json">Utf8Json</a> libraries.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">7 023,7 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 347,1 μs</td>
</tr>
<tr>
<td><strong>LargeWebApiZeroFormatterHttpWebRequest</strong></td>
<td style="text-align: right;">100</td>
<td style="text-align: right;"><strong>7 998,7 μs</strong></td>
</tr>
<tr>
<td><strong>LargeAspNetCoreZeroFormatterHttpWebRequest</strong></td>
<td style="text-align: right;">100</td>
<td style="text-align: right;"><strong>6 888,7 μs</strong></td>
</tr>
</tbody>
</table>
<p>It does pretty well, but it’s not quite as quick as MessagePack. Note that this test consists of serializing, then deserializing, followed by serializing some other stuff, and then finally deserializing that again. Deserializing might be ‘infinitely fast’, but serializing apparently is not.</p>
<h2 id="benchmark-code">Benchmark code</h2>
<p>In response to the previous article, I got a lot of questions from people who wanted to see the code I used to benchmark. There was a link hidden somewhere in the article, so let me now make it clear:</p>
<p style="font-size: 1.75em; font-weight:bold; color: black;">Here is the benchmark code: <a href="https://github.com/heemskerkerik/WcfVsBenchmark/tree/follow-up">GitHub</a></p>
<h2 id="new-results">New results</h2>
<p>The new results table is simply huge, because I’ve added a few serializers, and mostly because now every serializer/host combination based on ASP.NET is tested with <code>HttpClient</code> and <code>HttpWebRequest</code>. Making the sort of interactive graph from the previous post was a lot of work, and there’s too much uninteresting data, so I’ve decided to include a single graph of the following:</p>
<ul>
<li>Mean round-trip times for a single call, sending 100 ‘large’ objects to the server, in response to which the server sends 100 objects back to the client.</li>
<li>For ASP.NET Web API and ASP.NET Core MVC, all of the benchmarks use <code>HttpWebRequest</code>.</li>
</ul>
<div id="chart_div" style="width: 100%; height: 60vh"></div>
<p>The entire table of results is included here, but since it is so large, a <a href="https://raw.githubusercontent.com/heemskerkerik/WcfVsBenchmark/follow-up/BenchmarkResults.csv">CSV of the results</a> is also available, for easier viewing.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
<th style="text-align: right;">P95</th>
<th style="text-align: right;">Gen 0</th>
<th style="text-align: right;">Gen 1</th>
<th style="text-align: right;">Gen 2</th>
<th style="text-align: right;">Allocated</th>
</tr>
</thead>
<tbody>
<tr>
<td>SmallWcfText</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">336,5 μs</td>
<td style="text-align: right;">345,6 μs</td>
<td style="text-align: right;">8,7891</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">28,17 KB</td>
</tr>
<tr>
<td>SmallWcfWebXml</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">371,0 μs</td>
<td style="text-align: right;">377,6 μs</td>
<td style="text-align: right;">10,2539</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">31,66 KB</td>
</tr>
<tr>
<td>SmallWcfWebJson</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">380,2 μs</td>
<td style="text-align: right;">401,3 μs</td>
<td style="text-align: right;">10,2539</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">33,07 KB</td>
</tr>
<tr>
<td>SmallWcfBinary</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">344,4 μs</td>
<td style="text-align: right;">351,6 μs</td>
<td style="text-align: right;">8,7891</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">27,65 KB</td>
</tr>
<tr>
<td>SmallWcfNetTcp</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">108,7 μs</td>
<td style="text-align: right;">110,5 μs</td>
<td style="text-align: right;">1,4648</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">4,62 KB</td>
</tr>
<tr>
<td>SmallWcfMsgPackCli</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">368,4 μs</td>
<td style="text-align: right;">375,4 μs</td>
<td style="text-align: right;">10,2539</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">32,02 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">864,3 μs</td>
<td style="text-align: right;">934,3 μs</td>
<td style="text-align: right;">26,3672</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">85,28 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">906,8 μs</td>
<td style="text-align: right;">941,0 μs</td>
<td style="text-align: right;">25,3906</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">85,1 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">361,6 μs</td>
<td style="text-align: right;">368,2 μs</td>
<td style="text-align: right;">19,0430</td>
<td style="text-align: right;">6,8359</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">69,87 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">775,5 μs</td>
<td style="text-align: right;">884,3 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">61,4 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">855,8 μs</td>
<td style="text-align: right;">900,9 μs</td>
<td style="text-align: right;">16,6016</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">64,05 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">450,4 μs</td>
<td style="text-align: right;">476,3 μs</td>
<td style="text-align: right;">14,6484</td>
<td style="text-align: right;">5,3711</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">52,47 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 079,8 μs</td>
<td style="text-align: right;">1 105,8 μs</td>
<td style="text-align: right;">83,9844</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">264,17 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 119,8 μs</td>
<td style="text-align: right;">1 144,6 μs</td>
<td style="text-align: right;">85,9375</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">267,14 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">543,8 μs</td>
<td style="text-align: right;">555,6 μs</td>
<td style="text-align: right;">60,0586</td>
<td style="text-align: right;">11,2305</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">186,32 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">942,7 μs</td>
<td style="text-align: right;">957,5 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">104,33 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">951,0 μs</td>
<td style="text-align: right;">958,3 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">103,51 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">518,6 μs</td>
<td style="text-align: right;">525,3 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">13,1836</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">93,74 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">831,9 μs</td>
<td style="text-align: right;">883,6 μs</td>
<td style="text-align: right;">16,6016</td>
<td style="text-align: right;">6,8359</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">60,96 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">815,5 μs</td>
<td style="text-align: right;">902,7 μs</td>
<td style="text-align: right;">18,5547</td>
<td style="text-align: right;">6,8359</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">64,02 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">463,1 μs</td>
<td style="text-align: right;">480,3 μs</td>
<td style="text-align: right;">14,1602</td>
<td style="text-align: right;">5,3711</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">52,3 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">975,3 μs</td>
<td style="text-align: right;">1 033,9 μs</td>
<td style="text-align: right;">68,3594</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">225,09 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 035,9 μs</td>
<td style="text-align: right;">1 070,8 μs</td>
<td style="text-align: right;">72,2656</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">226,42 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">505,1 μs</td>
<td style="text-align: right;">523,0 μs</td>
<td style="text-align: right;">38,5742</td>
<td style="text-align: right;">10,2539</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">128,45 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">547,4 μs</td>
<td style="text-align: right;">618,6 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">55,33 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">779,5 μs</td>
<td style="text-align: right;">862,0 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">55,4 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">421,9 μs</td>
<td style="text-align: right;">443,5 μs</td>
<td style="text-align: right;">14,1602</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">43,97 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">508,3 μs</td>
<td style="text-align: right;">547,3 μs</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">38,97 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">520,3 μs</td>
<td style="text-align: right;">562,6 μs</td>
<td style="text-align: right;">13,6719</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">42,88 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">344,0 μs</td>
<td style="text-align: right;">358,0 μs</td>
<td style="text-align: right;">8,7891</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">28,42 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">797,8 μs</td>
<td style="text-align: right;">887,2 μs</td>
<td style="text-align: right;">76,1719</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">239,6 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">863,8 μs</td>
<td style="text-align: right;">1 022,6 μs</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">0,9766</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">244,78 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">474,4 μs</td>
<td style="text-align: right;">484,4 μs</td>
<td style="text-align: right;">52,2461</td>
<td style="text-align: right;">0,4883</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">163,6 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">742,2 μs</td>
<td style="text-align: right;">853,3 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">90,66 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">813,4 μs</td>
<td style="text-align: right;">927,5 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">90,54 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">460,9 μs</td>
<td style="text-align: right;">465,6 μs</td>
<td style="text-align: right;">26,3672</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">82,56 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">552,5 μs</td>
<td style="text-align: right;">638,9 μs</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">36,19 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">606,8 μs</td>
<td style="text-align: right;">719,9 μs</td>
<td style="text-align: right;">12,6953</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">39,32 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">431,3 μs</td>
<td style="text-align: right;">443,7 μs</td>
<td style="text-align: right;">7,3242</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">23,53 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">552,2 μs</td>
<td style="text-align: right;">579,3 μs</td>
<td style="text-align: right;">38,0859</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">121,4 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">550,5 μs</td>
<td style="text-align: right;">592,1 μs</td>
<td style="text-align: right;">38,0859</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">121,29 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">397,5 μs</td>
<td style="text-align: right;">408,5 μs</td>
<td style="text-align: right;">8,7891</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">27,08 KB</td>
</tr>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">337,7 μs</td>
<td style="text-align: right;">345,0 μs</td>
<td style="text-align: right;">8,7891</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">28,08 KB</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">365,2 μs</td>
<td style="text-align: right;">374,5 μs</td>
<td style="text-align: right;">10,2539</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">31,65 KB</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">376,6 μs</td>
<td style="text-align: right;">384,3 μs</td>
<td style="text-align: right;">10,7422</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">33,03 KB</td>
</tr>
<tr>
<td>LargeWcfBinary</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">352,9 μs</td>
<td style="text-align: right;">360,6 μs</td>
<td style="text-align: right;">8,7891</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">27,68 KB</td>
</tr>
<tr>
<td>LargeWcfNetTcp</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">109,9 μs</td>
<td style="text-align: right;">111,9 μs</td>
<td style="text-align: right;">1,4648</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">4,63 KB</td>
</tr>
<tr>
<td>LargeWcfMsgPackCli</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">374,4 μs</td>
<td style="text-align: right;">384,5 μs</td>
<td style="text-align: right;">10,2539</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">32,02 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">880,4 μs</td>
<td style="text-align: right;">919,6 μs</td>
<td style="text-align: right;">25,3906</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">85,29 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">915,1 μs</td>
<td style="text-align: right;">939,4 μs</td>
<td style="text-align: right;">25,3906</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">85,33 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">469,3 μs</td>
<td style="text-align: right;">492,4 μs</td>
<td style="text-align: right;">19,0430</td>
<td style="text-align: right;">6,3477</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">70,05 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">815,6 μs</td>
<td style="text-align: right;">902,9 μs</td>
<td style="text-align: right;">16,6016</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">61,22 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">855,1 μs</td>
<td style="text-align: right;">907,5 μs</td>
<td style="text-align: right;">18,5547</td>
<td style="text-align: right;">6,8359</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">64,4 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">446,9 μs</td>
<td style="text-align: right;">464,0 μs</td>
<td style="text-align: right;">14,1602</td>
<td style="text-align: right;">5,3711</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">52,24 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 121,9 μs</td>
<td style="text-align: right;">1 156,2 μs</td>
<td style="text-align: right;">83,9844</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">264,61 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 128,2 μs</td>
<td style="text-align: right;">1 163,0 μs</td>
<td style="text-align: right;">85,9375</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">267,55 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">589,9 μs</td>
<td style="text-align: right;">604,7 μs</td>
<td style="text-align: right;">60,0586</td>
<td style="text-align: right;">11,2305</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">186,38 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 007,8 μs</td>
<td style="text-align: right;">1 041,8 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">109,99 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">992,7 μs</td>
<td style="text-align: right;">1 032,8 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">109,03 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">445,5 μs</td>
<td style="text-align: right;">455,5 μs</td>
<td style="text-align: right;">30,2734</td>
<td style="text-align: right;">13,6719</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">97,01 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">862,8 μs</td>
<td style="text-align: right;">909,7 μs</td>
<td style="text-align: right;">18,5547</td>
<td style="text-align: right;">6,8359</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">63,89 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">345,4 μs</td>
<td style="text-align: right;">372,6 μs</td>
<td style="text-align: right;">12,2070</td>
<td style="text-align: right;">3,4180</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">46,63 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 035,9 μs</td>
<td style="text-align: right;">1 074,2 μs</td>
<td style="text-align: right;">72,2656</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">225,52 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 061,0 μs</td>
<td style="text-align: right;">1 093,3 μs</td>
<td style="text-align: right;">72,2656</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">226,44 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">471,1 μs</td>
<td style="text-align: right;">498,4 μs</td>
<td style="text-align: right;">38,0859</td>
<td style="text-align: right;">10,7422</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">128,25 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">635,3 μs</td>
<td style="text-align: right;">728,3 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">54,47 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">729,3 μs</td>
<td style="text-align: right;">861,8 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">55,96 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">429,4 μs</td>
<td style="text-align: right;">445,8 μs</td>
<td style="text-align: right;">14,1602</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">43,87 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">515,0 μs</td>
<td style="text-align: right;">560,5 μs</td>
<td style="text-align: right;">12,6953</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">39,26 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">521,9 μs</td>
<td style="text-align: right;">556,4 μs</td>
<td style="text-align: right;">13,6719</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">42,85 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">388,0 μs</td>
<td style="text-align: right;">400,9 μs</td>
<td style="text-align: right;">8,7891</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">28,29 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">831,8 μs</td>
<td style="text-align: right;">934,1 μs</td>
<td style="text-align: right;">77,1484</td>
<td style="text-align: right;">4,8828</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">241,45 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">956,7 μs</td>
<td style="text-align: right;">1 032,6 μs</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">245,13 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">501,2 μs</td>
<td style="text-align: right;">541,4 μs</td>
<td style="text-align: right;">51,7578</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">163,45 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">749,9 μs</td>
<td style="text-align: right;">865,8 μs</td>
<td style="text-align: right;">30,2734</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">95,37 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">935,4 μs</td>
<td style="text-align: right;">970,6 μs</td>
<td style="text-align: right;">30,2734</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">94,09 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">509,8 μs</td>
<td style="text-align: right;">527,5 μs</td>
<td style="text-align: right;">27,3438</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">86,38 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">562,7 μs</td>
<td style="text-align: right;">650,9 μs</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">36,19 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">684,5 μs</td>
<td style="text-align: right;">770,1 μs</td>
<td style="text-align: right;">12,6953</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">39,13 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">412,5 μs</td>
<td style="text-align: right;">419,6 μs</td>
<td style="text-align: right;">7,3242</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">23,54 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpClient</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">571,5 μs</td>
<td style="text-align: right;">615,6 μs</td>
<td style="text-align: right;">38,0859</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">121,21 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">584,2 μs</td>
<td style="text-align: right;">637,0 μs</td>
<td style="text-align: right;">38,0859</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">120,87 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">359,7 μs</td>
<td style="text-align: right;">389,0 μs</td>
<td style="text-align: right;">8,7891</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">27,12 KB</td>
</tr>
<tr>
<td>SmallWcfText</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">405,3 μs</td>
<td style="text-align: right;">413,9 μs</td>
<td style="text-align: right;">10,7422</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">33,33 KB</td>
</tr>
<tr>
<td>SmallWcfWebXml</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">415,1 μs</td>
<td style="text-align: right;">423,3 μs</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">36,86 KB</td>
</tr>
<tr>
<td>SmallWcfWebJson</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">557,9 μs</td>
<td style="text-align: right;">609,4 μs</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">38,75 KB</td>
</tr>
<tr>
<td>SmallWcfBinary</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">400,8 μs</td>
<td style="text-align: right;">408,5 μs</td>
<td style="text-align: right;">10,2539</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">32,46 KB</td>
</tr>
<tr>
<td>SmallWcfNetTcp</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">157,5 μs</td>
<td style="text-align: right;">161,4 μs</td>
<td style="text-align: right;">3,1738</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">9,92 KB</td>
</tr>
<tr>
<td>SmallWcfMsgPackCli</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">952,3 μs</td>
<td style="text-align: right;">1 043,0 μs</td>
<td style="text-align: right;">150,3906</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">472,75 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 020,6 μs</td>
<td style="text-align: right;">1 074,3 μs</td>
<td style="text-align: right;">27,3438</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">98,38 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 000,9 μs</td>
<td style="text-align: right;">1 045,6 μs</td>
<td style="text-align: right;">27,3438</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">98,99 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">427,8 μs</td>
<td style="text-align: right;">435,6 μs</td>
<td style="text-align: right;">25,8789</td>
<td style="text-align: right;">11,2305</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">82,71 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">908,3 μs</td>
<td style="text-align: right;">940,8 μs</td>
<td style="text-align: right;">19,5313</td>
<td style="text-align: right;">6,8359</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">67,41 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">917,1 μs</td>
<td style="text-align: right;">941,3 μs</td>
<td style="text-align: right;">21,4844</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">70,4 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">499,2 μs</td>
<td style="text-align: right;">513,9 μs</td>
<td style="text-align: right;">16,1133</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">58,45 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 235,5 μs</td>
<td style="text-align: right;">1 265,0 μs</td>
<td style="text-align: right;">93,7500</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">294,39 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 216,5 μs</td>
<td style="text-align: right;">1 234,6 μs</td>
<td style="text-align: right;">93,7500</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">294,71 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">573,8 μs</td>
<td style="text-align: right;">603,8 μs</td>
<td style="text-align: right;">57,6172</td>
<td style="text-align: right;">10,7422</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">199,3 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 065,3 μs</td>
<td style="text-align: right;">1 107,6 μs</td>
<td style="text-align: right;">35,1563</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">123,97 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 048,5 μs</td>
<td style="text-align: right;">1 087,9 μs</td>
<td style="text-align: right;">37,1094</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">125,52 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">526,8 μs</td>
<td style="text-align: right;">559,1 μs</td>
<td style="text-align: right;">30,2734</td>
<td style="text-align: right;">10,7422</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">107,18 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">956,0 μs</td>
<td style="text-align: right;">1 007,4 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">3,9063</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">67,12 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">990,9 μs</td>
<td style="text-align: right;">1 026,6 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">70,05 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">493,1 μs</td>
<td style="text-align: right;">513,4 μs</td>
<td style="text-align: right;">16,1133</td>
<td style="text-align: right;">6,8359</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">58,25 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 078,4 μs</td>
<td style="text-align: right;">1 126,8 μs</td>
<td style="text-align: right;">72,2656</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">240,91 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 090,9 μs</td>
<td style="text-align: right;">1 134,9 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">236,5 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">582,3 μs</td>
<td style="text-align: right;">601,0 μs</td>
<td style="text-align: right;">43,9453</td>
<td style="text-align: right;">12,6953</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">140,3 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">880,8 μs</td>
<td style="text-align: right;">945,4 μs</td>
<td style="text-align: right;">21,4844</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">68 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">911,8 μs</td>
<td style="text-align: right;">952,9 μs</td>
<td style="text-align: right;">22,4609</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">69,49 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">475,1 μs</td>
<td style="text-align: right;">490,0 μs</td>
<td style="text-align: right;">18,0664</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">55,92 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">630,9 μs</td>
<td style="text-align: right;">709,1 μs</td>
<td style="text-align: right;">13,6719</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">44,07 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">637,5 μs</td>
<td style="text-align: right;">758,3 μs</td>
<td style="text-align: right;">14,6484</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">46,57 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">390,9 μs</td>
<td style="text-align: right;">417,5 μs</td>
<td style="text-align: right;">10,2539</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">32,97 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 151,0 μs</td>
<td style="text-align: right;">1 183,6 μs</td>
<td style="text-align: right;">85,9375</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">266,94 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 151,1 μs</td>
<td style="text-align: right;">1 189,9 μs</td>
<td style="text-align: right;">85,9375</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">268,67 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">563,8 μs</td>
<td style="text-align: right;">581,4 μs</td>
<td style="text-align: right;">58,1055</td>
<td style="text-align: right;">0,4883</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">182,12 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 034,2 μs</td>
<td style="text-align: right;">1 073,7 μs</td>
<td style="text-align: right;">35,1563</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">110,05 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 057,3 μs</td>
<td style="text-align: right;">1 085,7 μs</td>
<td style="text-align: right;">33,2031</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">107,46 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">581,2 μs</td>
<td style="text-align: right;">609,5 μs</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">97,05 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">770,1 μs</td>
<td style="text-align: right;">850,2 μs</td>
<td style="text-align: right;">12,6953</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">41,85 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">813,2 μs</td>
<td style="text-align: right;">898,3 μs</td>
<td style="text-align: right;">13,6719</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">43,68 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">496,5 μs</td>
<td style="text-align: right;">512,9 μs</td>
<td style="text-align: right;">8,7891</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">28,22 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">681,4 μs</td>
<td style="text-align: right;">769,1 μs</td>
<td style="text-align: right;">43,9453</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">136,96 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">738,7 μs</td>
<td style="text-align: right;">897,4 μs</td>
<td style="text-align: right;">41,9922</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">132,09 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">375,9 μs</td>
<td style="text-align: right;">394,6 μs</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">37,27 KB</td>
</tr>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 416,8 μs</td>
<td style="text-align: right;">1 485,2 μs</td>
<td style="text-align: right;">35,1563</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">123,75 KB</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 465,3 μs</td>
<td style="text-align: right;">1 532,9 μs</td>
<td style="text-align: right;">35,1563</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">127,42 KB</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 704,8 μs</td>
<td style="text-align: right;">1 789,9 μs</td>
<td style="text-align: right;">42,9688</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">143,51 KB</td>
</tr>
<tr>
<td>LargeWcfBinary</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">948,7 μs</td>
<td style="text-align: right;">1 024,2 μs</td>
<td style="text-align: right;">35,1563</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">112,34 KB</td>
</tr>
<tr>
<td>LargeWcfNetTcp</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">745,7 μs</td>
<td style="text-align: right;">775,9 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">90,79 KB</td>
</tr>
<tr>
<td>LargeWcfMsgPackCli</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 952,6 μs</td>
<td style="text-align: right;">3 020,3 μs</td>
<td style="text-align: right;">316,4063</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 047,96 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 357,2 μs</td>
<td style="text-align: right;">2 422,9 μs</td>
<td style="text-align: right;">105,4688</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">352,19 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 250,2 μs</td>
<td style="text-align: right;">2 350,5 μs</td>
<td style="text-align: right;">105,4688</td>
<td style="text-align: right;">19,5313</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">352,43 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 906,3 μs</td>
<td style="text-align: right;">1 922,9 μs</td>
<td style="text-align: right;">87,8906</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">300,53 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 900,1 μs</td>
<td style="text-align: right;">1 928,1 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">256,83 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 899,3 μs</td>
<td style="text-align: right;">1 918,7 μs</td>
<td style="text-align: right;">76,1719</td>
<td style="text-align: right;">13,6719</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">257,94 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 033,2 μs</td>
<td style="text-align: right;">1 156,7 μs</td>
<td style="text-align: right;">64,4531</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">228,96 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">3 950,6 μs</td>
<td style="text-align: right;">4 195,4 μs</td>
<td style="text-align: right;">343,7500</td>
<td style="text-align: right;">101,5625</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 107,1 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">4 116,9 μs</td>
<td style="text-align: right;">4 250,3 μs</td>
<td style="text-align: right;">351,5625</td>
<td style="text-align: right;">101,5625</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 109,45 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">3 015,2 μs</td>
<td style="text-align: right;">3 107,9 μs</td>
<td style="text-align: right;">261,7188</td>
<td style="text-align: right;">42,9688</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">850,72 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 493,4 μs</td>
<td style="text-align: right;">2 552,4 μs</td>
<td style="text-align: right;">132,8125</td>
<td style="text-align: right;">19,5313</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">421,41 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 480,1 μs</td>
<td style="text-align: right;">2 564,5 μs</td>
<td style="text-align: right;">117,1875</td>
<td style="text-align: right;">23,4375</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">419,88 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 800,1 μs</td>
<td style="text-align: right;">1 889,4 μs</td>
<td style="text-align: right;">101,5625</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">352,29 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 038,8 μs</td>
<td style="text-align: right;">2 092,5 μs</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">274,06 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 255,3 μs</td>
<td style="text-align: right;">1 291,0 μs</td>
<td style="text-align: right;">68,3594</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">235,99 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 240,9 μs</td>
<td style="text-align: right;">2 296,1 μs</td>
<td style="text-align: right;">179,6875</td>
<td style="text-align: right;">23,4375</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">556,4 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 231,9 μs</td>
<td style="text-align: right;">2 296,6 μs</td>
<td style="text-align: right;">167,9688</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">551,97 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 086,4 μs</td>
<td style="text-align: right;">1 153,7 μs</td>
<td style="text-align: right;">140,6250</td>
<td style="text-align: right;">19,5313</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">453,42 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 379,7 μs</td>
<td style="text-align: right;">2 444,7 μs</td>
<td style="text-align: right;">101,5625</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">319,88 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 276,5 μs</td>
<td style="text-align: right;">2 367,5 μs</td>
<td style="text-align: right;">97,6563</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">314,11 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 850,3 μs</td>
<td style="text-align: right;">1 906,1 μs</td>
<td style="text-align: right;">80,0781</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">248,12 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 918,2 μs</td>
<td style="text-align: right;">1 968,2 μs</td>
<td style="text-align: right;">72,2656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">226,34 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 887,3 μs</td>
<td style="text-align: right;">1 928,9 μs</td>
<td style="text-align: right;">72,2656</td>
<td style="text-align: right;">3,9063</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">225,47 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 004,9 μs</td>
<td style="text-align: right;">1 092,8 μs</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">170,9 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">3 863,2 μs</td>
<td style="text-align: right;">4 074,2 μs</td>
<td style="text-align: right;">328,1250</td>
<td style="text-align: right;">39,0625</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 079,12 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">3 884,9 μs</td>
<td style="text-align: right;">4 069,2 μs</td>
<td style="text-align: right;">320,3125</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 076,46 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 934,6 μs</td>
<td style="text-align: right;">3 032,9 μs</td>
<td style="text-align: right;">261,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">810,53 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 441,5 μs</td>
<td style="text-align: right;">2 564,4 μs</td>
<td style="text-align: right;">121,0938</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">390,76 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 309,3 μs</td>
<td style="text-align: right;">2 418,6 μs</td>
<td style="text-align: right;">117,1875</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">387,58 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 832,4 μs</td>
<td style="text-align: right;">1 883,3 μs</td>
<td style="text-align: right;">87,8906</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">275,59 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 094,2 μs</td>
<td style="text-align: right;">2 132,6 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">232,16 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 001,8 μs</td>
<td style="text-align: right;">2 040,2 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">228,23 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 395,6 μs</td>
<td style="text-align: right;">1 437,7 μs</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">172,95 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpClient</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 051,5 μs</td>
<td style="text-align: right;">2 088,9 μs</td>
<td style="text-align: right;">136,7188</td>
<td style="text-align: right;">3,9063</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">422,55 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 042,6 μs</td>
<td style="text-align: right;">2 085,0 μs</td>
<td style="text-align: right;">132,8125</td>
<td style="text-align: right;">3,9063</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">419,67 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 037,5 μs</td>
<td style="text-align: right;">1 129,9 μs</td>
<td style="text-align: right;">103,5156</td>
<td style="text-align: right;">3,9063</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">325,47 KB</td>
</tr>
<tr>
<td>SmallWcfText</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">942,8 μs</td>
<td style="text-align: right;">1 056,0 μs</td>
<td style="text-align: right;">27,3438</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">89,15 KB</td>
</tr>
<tr>
<td>SmallWcfWebXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">912,8 μs</td>
<td style="text-align: right;">1 011,6 μs</td>
<td style="text-align: right;">30,2734</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">93,31 KB</td>
</tr>
<tr>
<td>SmallWcfWebJson</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 159,4 μs</td>
<td style="text-align: right;">1 255,2 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">92,44 KB</td>
</tr>
<tr>
<td>SmallWcfBinary</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">815,3 μs</td>
<td style="text-align: right;">893,2 μs</td>
<td style="text-align: right;">27,3438</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">86,43 KB</td>
</tr>
<tr>
<td>SmallWcfNetTcp</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">540,2 μs</td>
<td style="text-align: right;">592,0 μs</td>
<td style="text-align: right;">18,5547</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">59,29 KB</td>
</tr>
<tr>
<td>SmallWcfMsgPackCli</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 335,9 μs</td>
<td style="text-align: right;">1 456,1 μs</td>
<td style="text-align: right;">183,5938</td>
<td style="text-align: right;">27,3438</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">608,38 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 083,6 μs</td>
<td style="text-align: right;">2 130,4 μs</td>
<td style="text-align: right;">70,3125</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">242,81 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 984,6 μs</td>
<td style="text-align: right;">2 042,8 μs</td>
<td style="text-align: right;">70,3125</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">243,21 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 171,0 μs</td>
<td style="text-align: right;">1 260,1 μs</td>
<td style="text-align: right;">58,5938</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">208,64 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 212,0 μs</td>
<td style="text-align: right;">1 231,5 μs</td>
<td style="text-align: right;">37,1094</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">133,02 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 271,2 μs</td>
<td style="text-align: right;">1 424,3 μs</td>
<td style="text-align: right;">37,1094</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">132,95 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">832,0 μs</td>
<td style="text-align: right;">880,6 μs</td>
<td style="text-align: right;">36,1328</td>
<td style="text-align: right;">10,7422</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">117,93 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 451,0 μs</td>
<td style="text-align: right;">2 518,3 μs</td>
<td style="text-align: right;">179,6875</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">575,5 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 306,5 μs</td>
<td style="text-align: right;">2 411,4 μs</td>
<td style="text-align: right;">175,7813</td>
<td style="text-align: right;">19,5313</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">575,75 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 305,6 μs</td>
<td style="text-align: right;">1 341,5 μs</td>
<td style="text-align: right;">113,2813</td>
<td style="text-align: right;">13,6719</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">386,04 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 150,5 μs</td>
<td style="text-align: right;">2 200,4 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">269,46 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 998,5 μs</td>
<td style="text-align: right;">2 096,1 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">268,94 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 279,7 μs</td>
<td style="text-align: right;">1 325,1 μs</td>
<td style="text-align: right;">68,3594</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">242,99 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 582,7 μs</td>
<td style="text-align: right;">1 631,7 μs</td>
<td style="text-align: right;">39,0625</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">140,72 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 652,5 μs</td>
<td style="text-align: right;">1 770,8 μs</td>
<td style="text-align: right;">39,0625</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">141,04 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">755,1 μs</td>
<td style="text-align: right;">792,2 μs</td>
<td style="text-align: right;">36,1328</td>
<td style="text-align: right;">10,7422</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">117,63 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 350,9 μs</td>
<td style="text-align: right;">1 422,6 μs</td>
<td style="text-align: right;">103,5156</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">342,53 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 342,6 μs</td>
<td style="text-align: right;">1 396,2 μs</td>
<td style="text-align: right;">99,6094</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">340,31 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">683,0 μs</td>
<td style="text-align: right;">706,2 μs</td>
<td style="text-align: right;">72,2656</td>
<td style="text-align: right;">10,7422</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">245,19 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 069,1 μs</td>
<td style="text-align: right;">2 128,2 μs</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">200,16 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 951,7 μs</td>
<td style="text-align: right;">2 002,4 μs</td>
<td style="text-align: right;">66,4063</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">204,71 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 217,3 μs</td>
<td style="text-align: right;">1 367,3 μs</td>
<td style="text-align: right;">50,7813</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">161,44 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 065,4 μs</td>
<td style="text-align: right;">1 133,9 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">93,2 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 065,1 μs</td>
<td style="text-align: right;">1 169,8 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">93,98 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">629,9 μs</td>
<td style="text-align: right;">668,2 μs</td>
<td style="text-align: right;">24,4141</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">76,94 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 346,8 μs</td>
<td style="text-align: right;">2 440,4 μs</td>
<td style="text-align: right;">171,8750</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">542,28 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 178,9 μs</td>
<td style="text-align: right;">2 268,5 μs</td>
<td style="text-align: right;">171,8750</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">534,97 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 499,3 μs</td>
<td style="text-align: right;">1 571,4 μs</td>
<td style="text-align: right;">117,1875</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">363,42 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 121,8 μs</td>
<td style="text-align: right;">2 172,4 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">230,88 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 002,2 μs</td>
<td style="text-align: right;">2 041,1 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">232,82 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 406,0 μs</td>
<td style="text-align: right;">1 485,5 μs</td>
<td style="text-align: right;">64,4531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">202,55 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 402,6 μs</td>
<td style="text-align: right;">1 536,1 μs</td>
<td style="text-align: right;">35,1563</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">110,85 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 596,1 μs</td>
<td style="text-align: right;">1 766,4 μs</td>
<td style="text-align: right;">35,1563</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">112,05 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">799,6 μs</td>
<td style="text-align: right;">828,7 μs</td>
<td style="text-align: right;">24,4141</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">76,38 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 116,5 μs</td>
<td style="text-align: right;">1 164,2 μs</td>
<td style="text-align: right;">72,2656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">225,11 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 158,2 μs</td>
<td style="text-align: right;">1 217,4 μs</td>
<td style="text-align: right;">70,3125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">223,29 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">579,0 μs</td>
<td style="text-align: right;">612,3 μs</td>
<td style="text-align: right;">41,9922</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">131,31 KB</td>
</tr>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 306,5 μs</td>
<td style="text-align: right;">10 611,5 μs</td>
<td style="text-align: right;">234,3750</td>
<td style="text-align: right;">125,0000</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">1 260,97 KB</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 125,9 μs</td>
<td style="text-align: right;">10 441,0 μs</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">125,0000</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">1 265,46 KB</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">12 535,9 μs</td>
<td style="text-align: right;">12 877,7 μs</td>
<td style="text-align: right;">328,1250</td>
<td style="text-align: right;">171,8750</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">1 415,91 KB</td>
</tr>
<tr>
<td>LargeWcfBinary</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 889,3 μs</td>
<td style="text-align: right;">7 074,8 μs</td>
<td style="text-align: right;">242,1875</td>
<td style="text-align: right;">132,8125</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">1 142,53 KB</td>
</tr>
<tr>
<td>LargeWcfNetTcp</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">5 469,9 μs</td>
<td style="text-align: right;">5 734,8 μs</td>
<td style="text-align: right;">117,1875</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">707,86 KB</td>
</tr>
<tr>
<td>LargeWcfMsgPackCli</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">22 791,3 μs</td>
<td style="text-align: right;">23 427,8 μs</td>
<td style="text-align: right;">1 718,7500</td>
<td style="text-align: right;">562,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">6 327,24 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">17 566,7 μs</td>
<td style="text-align: right;">18 302,4 μs</td>
<td style="text-align: right;">718,7500</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">3 107,27 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">17 516,1 μs</td>
<td style="text-align: right;">18 712,4 μs</td>
<td style="text-align: right;">718,7500</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">3 104,68 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">14 204,2 μs</td>
<td style="text-align: right;">14 858,3 μs</td>
<td style="text-align: right;">687,5000</td>
<td style="text-align: right;">359,3750</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">2 649,91 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 292,9 μs</td>
<td style="text-align: right;">9 652,5 μs</td>
<td style="text-align: right;">562,5000</td>
<td style="text-align: right;">296,8750</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">2 471,18 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 372,5 μs</td>
<td style="text-align: right;">9 955,7 μs</td>
<td style="text-align: right;">546,8750</td>
<td style="text-align: right;">281,2500</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">2 468,98 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">7 023,7 μs</td>
<td style="text-align: right;">7 238,6 μs</td>
<td style="text-align: right;">539,0625</td>
<td style="text-align: right;">281,2500</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">2 334,58 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">26 691,1 μs</td>
<td style="text-align: right;">27 635,9 μs</td>
<td style="text-align: right;">2 781,2500</td>
<td style="text-align: right;">437,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">8 971,83 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">27 301,6 μs</td>
<td style="text-align: right;">28 104,1 μs</td>
<td style="text-align: right;">2 718,7500</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">8 964,38 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">28 510,5 μs</td>
<td style="text-align: right;">29 251,2 μs</td>
<td style="text-align: right;">2 156,2500</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">7 012,67 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">16 932,3 μs</td>
<td style="text-align: right;">17 843,6 μs</td>
<td style="text-align: right;">687,5000</td>
<td style="text-align: right;">343,7500</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">2 923,73 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">17 027,4 μs</td>
<td style="text-align: right;">17 928,7 μs</td>
<td style="text-align: right;">718,7500</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">2 929,49 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">12 461,1 μs</td>
<td style="text-align: right;">12 776,3 μs</td>
<td style="text-align: right;">703,1250</td>
<td style="text-align: right;">359,3750</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">2 508,79 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">13 971,3 μs</td>
<td style="text-align: right;">14 304,9 μs</td>
<td style="text-align: right;">609,3750</td>
<td style="text-align: right;">343,7500</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">3 675,47 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">11 023,6 μs</td>
<td style="text-align: right;">11 288,5 μs</td>
<td style="text-align: right;">609,3750</td>
<td style="text-align: right;">359,3750</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">3 482,26 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 964,6 μs</td>
<td style="text-align: right;">11 333,5 μs</td>
<td style="text-align: right;">765,6250</td>
<td style="text-align: right;">343,7500</td>
<td style="text-align: right;">46,8750</td>
<td style="text-align: right;">3 371,39 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">11 121,3 μs</td>
<td style="text-align: right;">11 440,2 μs</td>
<td style="text-align: right;">765,6250</td>
<td style="text-align: right;">406,2500</td>
<td style="text-align: right;">46,8750</td>
<td style="text-align: right;">3 371,05 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">7 998,7 μs</td>
<td style="text-align: right;">8 812,0 μs</td>
<td style="text-align: right;">734,3750</td>
<td style="text-align: right;">328,1250</td>
<td style="text-align: right;">46,8750</td>
<td style="text-align: right;">3 255,86 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">21 906,3 μs</td>
<td style="text-align: right;">22 576,2 μs</td>
<td style="text-align: right;">468,7500</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">3 203,19 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">21 717,5 μs</td>
<td style="text-align: right;">22 127,0 μs</td>
<td style="text-align: right;">531,2500</td>
<td style="text-align: right;">281,2500</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">3 202,07 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">18 784,1 μs</td>
<td style="text-align: right;">19 337,5 μs</td>
<td style="text-align: right;">406,2500</td>
<td style="text-align: right;">187,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">2 361,35 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">8 699,5 μs</td>
<td style="text-align: right;">9 226,8 μs</td>
<td style="text-align: right;">281,2500</td>
<td style="text-align: right;">156,2500</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">2 289,8 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">8 882,4 μs</td>
<td style="text-align: right;">9 442,3 μs</td>
<td style="text-align: right;">421,8750</td>
<td style="text-align: right;">234,3750</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">2 292,26 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 347,1 μs</td>
<td style="text-align: right;">6 514,6 μs</td>
<td style="text-align: right;">273,4375</td>
<td style="text-align: right;">148,4375</td>
<td style="text-align: right;">23,4375</td>
<td style="text-align: right;">1 986,16 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">25 200,5 μs</td>
<td style="text-align: right;">26 195,3 μs</td>
<td style="text-align: right;">1 625,0000</td>
<td style="text-align: right;">406,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">8 704,53 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">24 995,5 μs</td>
<td style="text-align: right;">25 570,6 μs</td>
<td style="text-align: right;">1 593,7500</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">8 693,94 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">26 311,1 μs</td>
<td style="text-align: right;">27 043,4 μs</td>
<td style="text-align: right;">1 218,7500</td>
<td style="text-align: right;">468,7500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">6 775,73 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">25 697,8 μs</td>
<td style="text-align: right;">26 587,6 μs</td>
<td style="text-align: right;">437,5000</td>
<td style="text-align: right;">218,7500</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">2 893,75 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">25 770,5 μs</td>
<td style="text-align: right;">26 730,3 μs</td>
<td style="text-align: right;">406,2500</td>
<td style="text-align: right;">218,7500</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">2 892,88 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">20 484,6 μs</td>
<td style="text-align: right;">22 693,2 μs</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">156,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">2 105,34 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">13 273,5 μs</td>
<td style="text-align: right;">13 686,2 μs</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">234,3750</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">3 663,91 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">13 432,0 μs</td>
<td style="text-align: right;">13 901,2 μs</td>
<td style="text-align: right;">359,3750</td>
<td style="text-align: right;">234,3750</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">3 663,88 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 944,2 μs</td>
<td style="text-align: right;">10 382,7 μs</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">203,1250</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">3 088,47 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpClient</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 847,4 μs</td>
<td style="text-align: right;">10 512,3 μs</td>
<td style="text-align: right;">406,2500</td>
<td style="text-align: right;">218,7500</td>
<td style="text-align: right;">46,8750</td>
<td style="text-align: right;">2 909,31 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 890,6 μs</td>
<td style="text-align: right;">10 254,3 μs</td>
<td style="text-align: right;">421,8750</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">46,8750</td>
<td style="text-align: right;">2 911,12 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">6 888,7 μs</td>
<td style="text-align: right;">7 243,7 μs</td>
<td style="text-align: right;">390,6250</td>
<td style="text-align: right;">210,9375</td>
<td style="text-align: right;">46,8750</td>
<td style="text-align: right;">2 802,42 KB</td>
</tr>
<tr>
<td>SmallWcfText</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">5 091,3 μs</td>
<td style="text-align: right;">5 333,0 μs</td>
<td style="text-align: right;">203,1250</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">646,88 KB</td>
</tr>
<tr>
<td>SmallWcfWebXml</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">5 170,4 μs</td>
<td style="text-align: right;">5 273,4 μs</td>
<td style="text-align: right;">195,3125</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">648,61 KB</td>
</tr>
<tr>
<td>SmallWcfWebJson</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">6 099,8 μs</td>
<td style="text-align: right;">6 294,8 μs</td>
<td style="text-align: right;">187,5000</td>
<td style="text-align: right;">39,0625</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">619,48 KB</td>
</tr>
<tr>
<td>SmallWcfBinary</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 486,1 μs</td>
<td style="text-align: right;">4 804,9 μs</td>
<td style="text-align: right;">187,5000</td>
<td style="text-align: right;">39,0625</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">621,5 KB</td>
</tr>
<tr>
<td>SmallWcfNetTcp</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 070,6 μs</td>
<td style="text-align: right;">4 243,7 μs</td>
<td style="text-align: right;">179,6875</td>
<td style="text-align: right;">39,0625</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">643,88 KB</td>
</tr>
<tr>
<td>SmallWcfMsgPackCli</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">5 262,8 μs</td>
<td style="text-align: right;">5 447,1 μs</td>
<td style="text-align: right;">601,5625</td>
<td style="text-align: right;">171,8750</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 872,26 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">8 703,8 μs</td>
<td style="text-align: right;">9 127,8 μs</td>
<td style="text-align: right;">484,3750</td>
<td style="text-align: right;">187,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 544,87 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">8 681,5 μs</td>
<td style="text-align: right;">9 036,5 μs</td>
<td style="text-align: right;">484,3750</td>
<td style="text-align: right;">187,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 543,54 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNetHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">6 954,6 μs</td>
<td style="text-align: right;">7 082,1 μs</td>
<td style="text-align: right;">445,3125</td>
<td style="text-align: right;">140,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 403,19 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 084,5 μs</td>
<td style="text-align: right;">4 484,5 μs</td>
<td style="text-align: right;">234,3750</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">770,84 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 234,1 μs</td>
<td style="text-align: right;">4 661,6 μs</td>
<td style="text-align: right;">234,3750</td>
<td style="text-align: right;">101,5625</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">770,07 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">3 052,9 μs</td>
<td style="text-align: right;">3 218,4 μs</td>
<td style="text-align: right;">218,7500</td>
<td style="text-align: right;">50,7813</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">700,45 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">11 586,3 μs</td>
<td style="text-align: right;">11 913,2 μs</td>
<td style="text-align: right;">1 078,1250</td>
<td style="text-align: right;">296,8750</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">3 395,91 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">11 734,6 μs</td>
<td style="text-align: right;">12 014,9 μs</td>
<td style="text-align: right;">1 093,7500</td>
<td style="text-align: right;">296,8750</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">3 394,1 KB</td>
</tr>
<tr>
<td>SmallWebApiMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">10 466,6 μs</td>
<td style="text-align: right;">10 964,3 μs</td>
<td style="text-align: right;">718,7500</td>
<td style="text-align: right;">234,3750</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">2 269,57 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">8 957,6 μs</td>
<td style="text-align: right;">9 437,3 μs</td>
<td style="text-align: right;">468,7500</td>
<td style="text-align: right;">156,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 649,38 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">9 080,0 μs</td>
<td style="text-align: right;">9 525,4 μs</td>
<td style="text-align: right;">453,1250</td>
<td style="text-align: right;">156,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 647,59 KB</td>
</tr>
<tr>
<td>SmallWebApiXmlHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">7 764,3 μs</td>
<td style="text-align: right;">8 270,7 μs</td>
<td style="text-align: right;">421,8750</td>
<td style="text-align: right;">140,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 451,44 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">5 008,2 μs</td>
<td style="text-align: right;">5 230,1 μs</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">840,26 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 981,5 μs</td>
<td style="text-align: right;">5 222,4 μs</td>
<td style="text-align: right;">273,4375</td>
<td style="text-align: right;">101,5625</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">842,22 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">3 526,5 μs</td>
<td style="text-align: right;">3 561,6 μs</td>
<td style="text-align: right;">234,3750</td>
<td style="text-align: right;">70,3125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">761,92 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 074,3 μs</td>
<td style="text-align: right;">4 389,9 μs</td>
<td style="text-align: right;">429,6875</td>
<td style="text-align: right;">210,9375</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 346,46 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 039,7 μs</td>
<td style="text-align: right;">4 393,4 μs</td>
<td style="text-align: right;">421,8750</td>
<td style="text-align: right;">210,9375</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 342,02 KB</td>
</tr>
<tr>
<td>SmallWebApiZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">2 794,2 μs</td>
<td style="text-align: right;">2 919,8 μs</td>
<td style="text-align: right;">390,6250</td>
<td style="text-align: right;">187,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 240,37 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">10 322,9 μs</td>
<td style="text-align: right;">10 687,6 μs</td>
<td style="text-align: right;">359,3750</td>
<td style="text-align: right;">109,3750</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 473,25 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">10 401,2 μs</td>
<td style="text-align: right;">10 776,3 μs</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">125,0000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 475,8 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNetHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">8 998,8 μs</td>
<td style="text-align: right;">9 751,7 μs</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">109,3750</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 235,5 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">3 970,5 μs</td>
<td style="text-align: right;">4 300,8 μs</td>
<td style="text-align: right;">179,6875</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">695,07 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">3 967,7 μs</td>
<td style="text-align: right;">4 317,4 μs</td>
<td style="text-align: right;">171,8750</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">695,65 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">2 990,7 μs</td>
<td style="text-align: right;">3 056,3 μs</td>
<td style="text-align: right;">167,9688</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">527,22 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">11 072,9 μs</td>
<td style="text-align: right;">11 383,8 μs</td>
<td style="text-align: right;">968,7500</td>
<td style="text-align: right;">203,1250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">3 286,54 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">11 269,9 μs</td>
<td style="text-align: right;">11 486,5 μs</td>
<td style="text-align: right;">953,1250</td>
<td style="text-align: right;">203,1250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">3 282,89 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">10 055,8 μs</td>
<td style="text-align: right;">10 532,7 μs</td>
<td style="text-align: right;">671,8750</td>
<td style="text-align: right;">171,8750</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">2 176,11 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">15 872,2 μs</td>
<td style="text-align: right;">16 587,9 μs</td>
<td style="text-align: right;">406,2500</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 623,8 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">16 132,0 μs</td>
<td style="text-align: right;">16 742,2 μs</td>
<td style="text-align: right;">437,5000</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 623,78 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXmlHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">12 898,5 μs</td>
<td style="text-align: right;">14 694,2 μs</td>
<td style="text-align: right;">328,1250</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 218,43 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 489,3 μs</td>
<td style="text-align: right;">5 041,2 μs</td>
<td style="text-align: right;">195,3125</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">745,89 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 834,4 μs</td>
<td style="text-align: right;">4 987,0 μs</td>
<td style="text-align: right;">226,5625</td>
<td style="text-align: right;">70,3125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">746,11 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">3 584,3 μs</td>
<td style="text-align: right;">3 676,0 μs</td>
<td style="text-align: right;">183,5938</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">577,3 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">3 279,5 μs</td>
<td style="text-align: right;">3 447,7 μs</td>
<td style="text-align: right;">308,5938</td>
<td style="text-align: right;">125,0000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 122,6 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">3 363,8 μs</td>
<td style="text-align: right;">3 592,3 μs</td>
<td style="text-align: right;">308,5938</td>
<td style="text-align: right;">136,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 123,26 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">2 542,6 μs</td>
<td style="text-align: right;">2 691,8 μs</td>
<td style="text-align: right;">203,1250</td>
<td style="text-align: right;">82,0313</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 019,71 KB</td>
</tr>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">113 203,8 μs</td>
<td style="text-align: right;">116 349,7 μs</td>
<td style="text-align: right;">3 062,5000</td>
<td style="text-align: right;">1 375,0000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">26 257,81 KB</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">113 749,8 μs</td>
<td style="text-align: right;">119 653,4 μs</td>
<td style="text-align: right;">3 000,0000</td>
<td style="text-align: right;">1 375,0000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">26 267,92 KB</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">124 571,2 μs</td>
<td style="text-align: right;">126 913,4 μs</td>
<td style="text-align: right;">3 562,5000</td>
<td style="text-align: right;">1 000,0000</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">19 550,39 KB</td>
</tr>
<tr>
<td>LargeWcfBinary</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">75 785,2 μs</td>
<td style="text-align: right;">77 798,0 μs</td>
<td style="text-align: right;">2 625,0000</td>
<td style="text-align: right;">1 062,5000</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">16 826,8 KB</td>
</tr>
<tr>
<td>LargeWcfNetTcp</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">63 496,8 μs</td>
<td style="text-align: right;">65 131,4 μs</td>
<td style="text-align: right;">1 437,5000</td>
<td style="text-align: right;">750,0000</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">15 259,22 KB</td>
</tr>
<tr>
<td>LargeWcfMsgPackCli</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">226 764,2 μs</td>
<td style="text-align: right;">228 210,0 μs</td>
<td style="text-align: right;">14 312,5000</td>
<td style="text-align: right;">2 062,5000</td>
<td style="text-align: right;">812,5000</td>
<td style="text-align: right;">63 616,09 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">146 788,7 μs</td>
<td style="text-align: right;">150 342,6 μs</td>
<td style="text-align: right;">6 062,5000</td>
<td style="text-align: right;">1 000,0000</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">28 004,73 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">148 252,3 μs</td>
<td style="text-align: right;">150 701,1 μs</td>
<td style="text-align: right;">6 125,0000</td>
<td style="text-align: right;">1 000,0000</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">28 005,47 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNetHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">137 693,6 μs</td>
<td style="text-align: right;">140 988,9 μs</td>
<td style="text-align: right;">6 187,5000</td>
<td style="text-align: right;">1 000,0000</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">24 196,63 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">76 918,2 μs</td>
<td style="text-align: right;">79 686,3 μs</td>
<td style="text-align: right;">5 500,0000</td>
<td style="text-align: right;">1 187,5000</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">26 612,74 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">79 844,3 μs</td>
<td style="text-align: right;">82 522,0 μs</td>
<td style="text-align: right;">5 500,0000</td>
<td style="text-align: right;">1 187,5000</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">26 607,16 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePackHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">71 342,8 μs</td>
<td style="text-align: right;">73 000,7 μs</td>
<td style="text-align: right;">5 437,5000</td>
<td style="text-align: right;">1 187,5000</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">25 423,91 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">231 637,3 μs</td>
<td style="text-align: right;">233 815,8 μs</td>
<td style="text-align: right;">27 187,5000</td>
<td style="text-align: right;">1 875,0000</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">87 423,5 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">235 545,0 μs</td>
<td style="text-align: right;">237 502,2 μs</td>
<td style="text-align: right;">26 812,5000</td>
<td style="text-align: right;">1 750,0000</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">87 424,05 KB</td>
</tr>
<tr>
<td>LargeWebApiMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">273 161,6 μs</td>
<td style="text-align: right;">274 337,6 μs</td>
<td style="text-align: right;">21 375,0000</td>
<td style="text-align: right;">1 625,0000</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">68 248,17 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">138 851,2 μs</td>
<td style="text-align: right;">140 807,8 μs</td>
<td style="text-align: right;">6 500,0000</td>
<td style="text-align: right;">1 375,0000</td>
<td style="text-align: right;">500,0000</td>
<td style="text-align: right;">32 739,53 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">137 728,4 μs</td>
<td style="text-align: right;">139 416,2 μs</td>
<td style="text-align: right;">6 437,5000</td>
<td style="text-align: right;">1 250,0000</td>
<td style="text-align: right;">500,0000</td>
<td style="text-align: right;">32 737,39 KB</td>
</tr>
<tr>
<td>LargeWebApiXmlHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">117 583,8 μs</td>
<td style="text-align: right;">119 814,8 μs</td>
<td style="text-align: right;">6 312,5000</td>
<td style="text-align: right;">1 125,0000</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">27 616,55 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">108 526,5 μs</td>
<td style="text-align: right;">110 023,5 μs</td>
<td style="text-align: right;">5 937,5000</td>
<td style="text-align: right;">1 250,0000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">36 588,36 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">99 423,4 μs</td>
<td style="text-align: right;">101 303,8 μs</td>
<td style="text-align: right;">5 875,0000</td>
<td style="text-align: right;">1 250,0000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">34 853,14 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">93 279,3 μs</td>
<td style="text-align: right;">95 066,0 μs</td>
<td style="text-align: right;">6 000,0000</td>
<td style="text-align: right;">1 875,0000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">34 568,77 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">93 082,5 μs</td>
<td style="text-align: right;">96 336,2 μs</td>
<td style="text-align: right;">6 125,0000</td>
<td style="text-align: right;">1 812,5000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">34 556,73 KB</td>
</tr>
<tr>
<td>LargeWebApiZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">83 440,5 μs</td>
<td style="text-align: right;">87 326,7 μs</td>
<td style="text-align: right;">6 062,5000</td>
<td style="text-align: right;">1 750,0000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">35 920,83 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">190 051,4 μs</td>
<td style="text-align: right;">193 029,4 μs</td>
<td style="text-align: right;">4 375,0000</td>
<td style="text-align: right;">1 187,5000</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">29 453,69 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">191 288,1 μs</td>
<td style="text-align: right;">194 703,0 μs</td>
<td style="text-align: right;">4 312,5000</td>
<td style="text-align: right;">1 000,0000</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">29 435,33 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNetHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">172 396,6 μs</td>
<td style="text-align: right;">174 307,9 μs</td>
<td style="text-align: right;">4 000,0000</td>
<td style="text-align: right;">750,0000</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">22 879,07 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">74 707,4 μs</td>
<td style="text-align: right;">76 628,3 μs</td>
<td style="text-align: right;">2 937,5000</td>
<td style="text-align: right;">1 062,5000</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">24 513,18 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">75 239,7 μs</td>
<td style="text-align: right;">77 764,9 μs</td>
<td style="text-align: right;">3 062,5000</td>
<td style="text-align: right;">875,0000</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">24 503,66 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePackHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">64 765,4 μs</td>
<td style="text-align: right;">67 297,2 μs</td>
<td style="text-align: right;">2 750,0000</td>
<td style="text-align: right;">1 000,0000</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">22 184,38 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">224 174,1 μs</td>
<td style="text-align: right;">226 700,3 μs</td>
<td style="text-align: right;">16 000,0000</td>
<td style="text-align: right;">1 875,0000</td>
<td style="text-align: right;">500,0000</td>
<td style="text-align: right;">84 368,61 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">226 491,1 μs</td>
<td style="text-align: right;">229 350,5 μs</td>
<td style="text-align: right;">15 625,0000</td>
<td style="text-align: right;">1 562,5000</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">84 367,02 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMsgPackCliHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">259 125,8 μs</td>
<td style="text-align: right;">260 689,1 μs</td>
<td style="text-align: right;">12 437,5000</td>
<td style="text-align: right;">1 625,0000</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">66 232,65 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">187 939,3 μs</td>
<td style="text-align: right;">191 380,4 μs</td>
<td style="text-align: right;">4 062,5000</td>
<td style="text-align: right;">1 062,5000</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">29 287,81 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">186 831,9 μs</td>
<td style="text-align: right;">191 377,0 μs</td>
<td style="text-align: right;">3 750,0000</td>
<td style="text-align: right;">1 000,0000</td>
<td style="text-align: right;">375,0000</td>
<td style="text-align: right;">29 274,29 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXmlHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">163 545,0 μs</td>
<td style="text-align: right;">166 214,4 μs</td>
<td style="text-align: right;">3 312,5000</td>
<td style="text-align: right;">750,0000</td>
<td style="text-align: right;">125,0000</td>
<td style="text-align: right;">21 599,57 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">112 656,4 μs</td>
<td style="text-align: right;">114 328,7 μs</td>
<td style="text-align: right;">3 750,0000</td>
<td style="text-align: right;">1 500,0000</td>
<td style="text-align: right;">875,0000</td>
<td style="text-align: right;">35 651,9 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">113 442,3 μs</td>
<td style="text-align: right;">116 928,0 μs</td>
<td style="text-align: right;">3 750,0000</td>
<td style="text-align: right;">1 500,0000</td>
<td style="text-align: right;">937,5000</td>
<td style="text-align: right;">35 638,04 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8JsonHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">91 813,6 μs</td>
<td style="text-align: right;">93 977,9 μs</td>
<td style="text-align: right;">3 187,5000</td>
<td style="text-align: right;">1 250,0000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">31 173,77 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpClient</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">89 996,3 μs</td>
<td style="text-align: right;">92 385,9 μs</td>
<td style="text-align: right;">3 375,0000</td>
<td style="text-align: right;">1 562,5000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">31 020,04 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpClientAsync</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">89 334,9 μs</td>
<td style="text-align: right;">92 773,4 μs</td>
<td style="text-align: right;">3 312,5000</td>
<td style="text-align: right;">1 562,5000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">31 017,05 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreZeroFormatterHttpWebRequest</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">81 701,0 μs</td>
<td style="text-align: right;">85 651,5 μs</td>
<td style="text-align: right;">3 000,0000</td>
<td style="text-align: right;">1 437,5000</td>
<td style="text-align: right;">500,0000</td>
<td style="text-align: right;">32 338,78 KB</td>
</tr>
</tbody>
</table>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2018/04/Achtergond-8.2@2x-1.png" medium="image" /></item><item><title>Is WCF faster than ASP.NET? Of course not! Or is it?</title><description>How does WCF, a 13-year-old mega-abstraction framework hold up against the modern, lean, ASP.NET Core? You’d be surprised.</description><link>https://www.erikheemskerk.nl/benchmark-wcf-webapi-aspnetcore-mvc/</link><guid isPermaLink="false">605618814fc9bc45be1c9a77</guid><category>Benchmarking</category><category>.NET Core</category><category>MessagePack</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Thu, 22 Mar 2018 11:20:54 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2018/03/Achtergond-8.1-groot.png" alt="Is WCF faster than ASP.NET? Of course not! Or is it?" /><p>I was casually browsing Reddit when I came across a comment that triggered me. I’m paraphrasing<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>, but the gist of it was that ‘WCF is faster than Web API or ASP.NET Core’. Surely, that was a mistake.</p>
<figure>
<p><img src="/content/images/2018/03/duty_calls.png" alt="Are you coming to bed? - I can’t. This is important. - What? - Someone is wrong on the internet." /></p>
<figcaption>“<a href="https://xkcd.com/386/">Duty Calls</a>” by <a href="https://xkcd.com">xkcd</a> is licensed under <a href="https://creativecommons.org/licenses/by-nc/2.5/">CC BY-NC 2.5</a>.</figcaption>
</figure>
<p>Boy, I’ll show them. What are they claiming, actually? ‘The response times of a WCF service are much lower than those of ASP.NET Web API or ASP.NET Core MVC.’ Pfft. I’ll just write a small benchmark using trusty ol’ <a href="http://benchmarkdotnet.org/index.htm">BenchmarkDotNet</a>. Stand up a local web server, measure how long it takes to create a request, send it, deserialize it, generate a response, send that back, and deserialize the response. One method will use WCF, the other will use ASP.NET Web API. To quote a colleague of mine: ‘how hard could it be?’</p>
<blockquote>
<p>If you’re only interested in the complete picture, take a look at <a href="#results">the full table of results and the graphs</a>.</p>
</blockquote>
<h2 id="first-steps">First steps</h2>
<p>I’m sending 100 very simple objects (a single property that is a GUID) to the API and it’s sending 100 items back. Writing the benchmark wasn’t hard, but processing the outcome was, kind of. WCF <strong>was</strong> faster than ASP.NET Web API.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>Wcf</td>
<td style="text-align: right;">830,1 μs</td>
</tr>
<tr>
<td>WebApi</td>
<td style="text-align: right;">2 614,2 μs</td>
</tr>
</tbody>
</table>
<p>And not just a little bit faster; WCF is putting Web API to shame by taking less than a third of the time. Alright, but Web API is a pretty obsolete technology. Surely the new and shiny ASP.NET Core MVC will do much better. Right?</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>Wcf</td>
<td style="text-align: right;">830,1 μs</td>
</tr>
<tr>
<td>WebApi</td>
<td style="text-align: right;">2 614,2 μs</td>
</tr>
<tr>
<td><strong>AspNetCore</strong></td>
<td style="text-align: right;"><strong>2 524,8 μs</strong></td>
</tr>
</tbody>
</table>
<p>Well, it’s a <em>little</em> faster, but WCF still takes less than a third of the time.</p>
<h2 id="different-formats">Different formats</h2>
<p>What’s so different between WCF and the other two options? Well, there is a pretty obvious difference: WCF is serializing data to and from XML (SOAP, to be precise), while for Web API and ASP.NET Core MVC, I was defaulting to JSON. What if I force the APIs to use XML?</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>Wcf</td>
<td style="text-align: right;">830,1 μs</td>
</tr>
<tr>
<td>WebApiJson</td>
<td style="text-align: right;">2 614,2 μs</td>
</tr>
<tr>
<td>AspNetCoreJson</td>
<td style="text-align: right;">2 524,8 μs</td>
</tr>
<tr>
<td><strong>WebApiXml</strong></td>
<td style="text-align: right;"><strong>1 982,7 μs</strong></td>
</tr>
<tr>
<td><strong>AspNetCoreXml</strong></td>
<td style="text-align: right;"><strong>1 933,5 μs</strong></td>
</tr>
</tbody>
</table>
<p>There’s a definite improvement there. WCF is still a lot faster, but at least this shows we can get better results by picking a different serializer.</p>
<h2 id="messagepack">MessagePack</h2>
<p>So what’s the fastest serializer we can find? According to their own claims, <a href="https://msgpack.org/index.html">MessagePack</a> is a small and fast format, and Yoshifumi Kawai has written a <a href="https://github.com/neuecc/MessagePack-CSharp">high-performance MessagePack serializer</a> for .NET that beats <a href="https://github.com/mgravell/protobuf-net">protobuf-net</a>, the default Protobuf serializer for .NET. Protobuf was designed to be easy to serialize and is known for its performance. Let’s see.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>Wcf</td>
<td style="text-align: right;">830,1 μs</td>
</tr>
<tr>
<td>WebApiJson</td>
<td style="text-align: right;">2 614,2 μs</td>
</tr>
<tr>
<td>AspNetCoreJson</td>
<td style="text-align: right;">2 524,8 μs</td>
</tr>
<tr>
<td>WebApiXml</td>
<td style="text-align: right;">1 982,7 μs</td>
</tr>
<tr>
<td>AspNetCoreXml</td>
<td style="text-align: right;">1 933,5 μs</td>
</tr>
<tr>
<td><strong>WebApiMessagePack</strong></td>
<td style="text-align: right;"><strong>1 615,7 μs</strong></td>
</tr>
<tr>
<td><strong>AspNetCoreMessagePack</strong></td>
<td style="text-align: right;"><strong>1 483,8 μs</strong></td>
</tr>
</tbody>
</table>
<p>It’s going in the right direction. But can we go any faster? Probably not without extensive customization. However, these benchmarks have all been about serializing and deserializing 100 very simple objects. In the real world, objects are usually more complex than just a single GUID property.</p>
<h2 id="larger-objects">Larger objects</h2>
<p>Let’s try a more complex object.</p>
<pre><code class="language-csharp">public class LargeItem
{
    public Guid OrderId { get; set; }
    public ulong OrderNumber { get; set; }
    public string EmailAddress { get; set; }
    public Address ShippingAddress { get; set; } = new Address();
    public Address InvoiceAddress { get; set; } = new Address();
    public DateTimeOffset RequestedDeliveryDate { get; set; }
    public decimal ShippingCosts { get; set; }
    public DateTimeOffset LastModified { get; set; }
    public Guid CreateNonce { get; set; }
    public List&lt;OrderLine&gt; OrderLines { get; set; }
}

public class OrderLine
{
    public string Sku { get; set; }
    public int Quantity { get; set; }
    public string Product { get; set; }
    public decimal Price { get; set; }
}

public class Address
{
    public string Name { get; set; }
    public string Street { get; set; }
    public string HouseNumber { get; set; }
    public string PostalCode { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
}
</code></pre>
<p>This is an order with order lines, copied from one of my other projects. There is a reasonable number of properties there, so let’s see how it goes.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeWcf</td>
<td style="text-align: right;">9 890,2 μs</td>
</tr>
<tr>
<td>LargeWebApiJson</td>
<td style="text-align: right;">25 425,6 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreJson</td>
<td style="text-align: right;">40 312,9 μs</td>
</tr>
<tr>
<td>LargeWebApiXml</td>
<td style="text-align: right;">16 193,5 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreXml</td>
<td style="text-align: right;">36 767,1 μs</td>
</tr>
<tr>
<td>LargeWebApiMessagePack</td>
<td style="text-align: right;">8 834,9 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePack</td>
<td style="text-align: right;">8 813,8 μs</td>
</tr>
</tbody>
</table>
<p>Bingo. When serializing and deserializing 100 ‘large’ items, MessagePack on either ASP.NET Web API or ASP.NET Core MVC beats WCF by a small margin.</p>
<h2 id="itdepends">#itdepends</h2>
<p>As usual, the answer to the question ‘which is faster’ is: it depends. When performance is absolutely critical, and your service doesn’t have to be a public API that anyone can consume, WCF is probably your best bet, up to a certain point. When you need to communicate large amounts of complex data, MessagePack on top of ASP.NET Core MVC is probably a better solution. Also worth considering is the developer-friendliness, where WCF doesn’t score very high marks.</p>
<p>What about JSON? The default library for working with JSON in .NET is Newtonsoft.Json. It’s great when you have to have absolute control over how your JSON looks, but it’s by far the slowest option I’ve examined. Can we have our cake and eat it too?</p>
<h2 id="utf8json">Utf8Json</h2>
<p>It turns out that, yes, we can. Sort of. Yoshifumi Kawai, who you’ll remember as the author of the very fast MessagePack serializer for .NET, has also written a performance-oriented JSON serializer for .NET called <a href="https://github.com/neuecc/Utf8Json">Utf8Json</a>. It’s not as customizable and it doesn’t support DOM operations, but man, is it fast.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>LargeWcf</td>
<td style="text-align: right;">9 890,2 μs</td>
</tr>
<tr>
<td>LargeWebApiJson</td>
<td style="text-align: right;">25 425,6 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreJson</td>
<td style="text-align: right;">40 312,9 μs</td>
</tr>
<tr>
<td>LargeWebApiXml</td>
<td style="text-align: right;">16 193,5 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreXml</td>
<td style="text-align: right;">36 767,1 μs</td>
</tr>
<tr>
<td>LargeWebApiMessagePack</td>
<td style="text-align: right;">8 834,9 μs</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePack</td>
<td style="text-align: right;">8 813,8 μs</td>
</tr>
<tr>
<td><strong>LargeWebApiUtf8Json</strong></td>
<td style="text-align: right;"><strong>13 787,9 μs</strong></td>
</tr>
<tr>
<td><strong>LargeAspNetCoreUtf8Json</strong></td>
<td style="text-align: right;"><strong>13 961,1 μs</strong></td>
</tr>
</tbody>
</table>
<p>It’s not as fast as WCF, and definitely not as fast as MessagePack, but it easily takes less than half the time that Newtonsoft.Json does. It does this by avoiding memory allocations (similar to the MessagePack serializer) and by treating JSON as a binary format; it doesn’t serialize to a string which is then converted into bytes, but instead it serializes straight to UTF-8 bytes<a id="fnref:2" href="#fn:2" class="footnote-ref"><sup>2</sup></a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>As I said before, it depends. If round-trip time performance is more important than pretty much anything else, but you still want to use a managed language, use WCF. If you care about performance, but also about legible and easy to use code, or you want to create a proper REST-ful service, use MessagePack or Utf8Json on top of ASP.NET Core MVC or ASP.NET Web API.</p>
<p>The code I’ve used to benchmark these libraries is available on <a href="https://github.com/heemskerkerik/WcfVsBenchmark">GitHub</a>. Feel free to play around with it, and maybe find even better options.</p>
<h2 id="data">Data</h2>
<p>Below you’ll find the ‘raw’ output from the benchmark I’ve run. Some annotations:</p>
<ul>
<li>A method prefixed with ‘Small’ means it serializes and deserializes a small class with only a single GUID property. ‘Large’ means it serializes and deserializes the large class described above.</li>
<li>All WCF methods are hosted using <code>WebServiceHost</code>;</li>
<li>All WCF methods use a <code>ChannelFactory</code> client, while the Web API and ASP.NET Core MVC methods use <code>System.Net.HttpClient</code><a id="fnref:3" href="#fn:3" class="footnote-ref"><sup>3</sup></a>;</li>
<li><code>WcfText</code> uses a <code>BasicHttpBinding</code> with the message encoding set to <code>Text</code>;</li>
<li><code>WcfWebXml</code> and <code>WcfWebJson</code> use a <code>WebHttpBinding</code>, with, respectively, XML or JSON as the message format;</li>
<li><code>JsonNet</code> methods use Newtonsoft.Json to serialize objects;</li>
<li>I’ve included ‘0 items’ as an indication of how fast the client and web server/framework are;</li>
<li>The ‘P95’ column represents the 95th <a href="https://en.wikipedia.org/wiki/Percentile">percentile</a> value of the response time;</li>
<li>The ‘Gen 0’, ‘Gen 1’ and ‘Gen 2’ columns represent the number of garbage collections per 1 000 invocations;</li>
<li>The ‘Allocated’ column represents the total amount of memory allocated for each invocation;</li>
</ul>
<p>Some interesting data points that jump out:</p>
<ul>
<li>At 0 items, ASP.NET Core makes significantly fewer allocations, and as a result, it is the only option that results in <em>no</em> Generation 1 (or higher) allocations;</li>
<li>Even with 0 items, the options using Newtonsoft.Json are a lot slower than any of the other options. There must be a lot of initialization happening;</li>
<li>For larger item sizes, Utf8Json is frequently allocating the most memory of any option, but it still manages to be pretty fast. That just goes to show - avoiding memory allocation isn’t the only thing you need for speed;</li>
<li>ASP.NET Core’s XML serializer is a lot slower than Web API’s - this is particularly visible for large items;</li>
<li>WCF does <em>very well</em> in terms of allocated memory. If memory is constrained, WCF might be a viable option;</li>
</ul>
<p>A note on the chart: I’ve tried my best to make it look good on desktop and on mobile, but the viewing experience is still best on a large screen.</p>
<p><a name="results" style="display:block;position:relative;visibility: hidden;top:-50px"></a></p>
<table>
<thead>
<tr>
<th>Method</th>
<th style="text-align: right;">ItemCount</th>
<th style="text-align: right;">Mean</th>
<th style="text-align: right;">P95</th>
<th style="text-align: right;">Gen 0</th>
<th style="text-align: right;">Gen 1</th>
<th style="text-align: right;">Gen 2</th>
<th style="text-align: right;">Allocated</th>
</tr>
</thead>
<tbody>
<tr>
<td>SmallWcfText</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">365,8 μs</td>
<td style="text-align: right;">383,1 μs</td>
<td style="text-align: right;">8,3008</td>
<td style="text-align: right;">1,4648</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">30,08 KB</td>
</tr>
<tr>
<td>SmallWcfWebXml</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">378,9 μs</td>
<td style="text-align: right;">392,0 μs</td>
<td style="text-align: right;">9,2773</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">33,28 KB</td>
</tr>
<tr>
<td>SmallWcfWebJson</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">396,1 μs</td>
<td style="text-align: right;">421,4 μs</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">34,6 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNet</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 802,4 μs</td>
<td style="text-align: right;">1 904,6 μs</td>
<td style="text-align: right;">35,1563</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">122,74 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePack</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">931,8 μs</td>
<td style="text-align: right;">955,5 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">62,95 KB</td>
</tr>
<tr>
<td>SmallWebApiXml</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">997,7 μs</td>
<td style="text-align: right;">1 067,0 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">105,28 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8Json</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">925,5 μs</td>
<td style="text-align: right;">939,4 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">66,17 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNet</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 753,1 μs</td>
<td style="text-align: right;">1 776,1 μs</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">99,3 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePack</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">853,2 μs</td>
<td style="text-align: right;">898,7 μs</td>
<td style="text-align: right;">14,6484</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">46,21 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXml</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">955,9 μs</td>
<td style="text-align: right;">1 014,9 μs</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">98,16 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8Json</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">914,4 μs</td>
<td style="text-align: right;">957,6 μs</td>
<td style="text-align: right;">12,6953</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">41,6 KB</td>
</tr>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">353,1 μs</td>
<td style="text-align: right;">361,0 μs</td>
<td style="text-align: right;">8,3008</td>
<td style="text-align: right;">1,4648</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">30,11 KB</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">374,8 μs</td>
<td style="text-align: right;">382,4 μs</td>
<td style="text-align: right;">9,2773</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">33,27 KB</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">386,9 μs</td>
<td style="text-align: right;">398,0 μs</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">34,58 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNet</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 860,5 μs</td>
<td style="text-align: right;">1 900,6 μs</td>
<td style="text-align: right;">50,7813</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">183,1 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePack</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">924,2 μs</td>
<td style="text-align: right;">937,6 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">62,22 KB</td>
</tr>
<tr>
<td>LargeWebApiXml</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 000,3 μs</td>
<td style="text-align: right;">1 110,8 μs</td>
<td style="text-align: right;">32,2266</td>
<td style="text-align: right;">10,7422</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">109,81 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8Json</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">929,3 μs</td>
<td style="text-align: right;">939,1 μs</td>
<td style="text-align: right;">19,5313</td>
<td style="text-align: right;">6,8359</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">66,79 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNet</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">1 848,1 μs</td>
<td style="text-align: right;">1 915,2 μs</td>
<td style="text-align: right;">50,7813</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">159,7 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePack</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">881,4 μs</td>
<td style="text-align: right;">918,2 μs</td>
<td style="text-align: right;">14,6484</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">46,27 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXml</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">943,4 μs</td>
<td style="text-align: right;">974,8 μs</td>
<td style="text-align: right;">33,2031</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">104,96 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8Json</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">943,7 μs</td>
<td style="text-align: right;">991,1 μs</td>
<td style="text-align: right;">12,6953</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">41,61 KB</td>
</tr>
<tr>
<td>SmallWcfText</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">465,0 μs</td>
<td style="text-align: right;">534,4 μs</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">35,29 KB</td>
</tr>
<tr>
<td>SmallWcfWebXml</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">472,3 μs</td>
<td style="text-align: right;">516,5 μs</td>
<td style="text-align: right;">11,2305</td>
<td style="text-align: right;">2,4414</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">38,44 KB</td>
</tr>
<tr>
<td>SmallWcfWebJson</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">457,7 μs</td>
<td style="text-align: right;">467,2 μs</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">2,4414</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">39,84 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNet</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 856,1 μs</td>
<td style="text-align: right;">1 890,8 μs</td>
<td style="text-align: right;">42,9688</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">152,07 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePack</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">930,9 μs</td>
<td style="text-align: right;">940,6 μs</td>
<td style="text-align: right;">17,5781</td>
<td style="text-align: right;">6,8359</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">67,73 KB</td>
</tr>
<tr>
<td>SmallWebApiXml</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 267,1 μs</td>
<td style="text-align: right;">1 463,6 μs</td>
<td style="text-align: right;">33,2031</td>
<td style="text-align: right;">5,8594</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">124,98 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8Json</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">960,2 μs</td>
<td style="text-align: right;">1 027,8 μs</td>
<td style="text-align: right;">21,4844</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">74,07 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNet</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 899,6 μs</td>
<td style="text-align: right;">1 950,9 μs</td>
<td style="text-align: right;">41,0156</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">128,06 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePack</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">902,6 μs</td>
<td style="text-align: right;">933,2 μs</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">50,96 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXml</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 051,0 μs</td>
<td style="text-align: right;">1 186,4 μs</td>
<td style="text-align: right;">37,1094</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">118 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8Json</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">996,0 μs</td>
<td style="text-align: right;">1 057,8 μs</td>
<td style="text-align: right;">13,6719</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">46,21 KB</td>
</tr>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 471,8 μs</td>
<td style="text-align: right;">1 731,5 μs</td>
<td style="text-align: right;">35,1563</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">125,85 KB</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 430,8 μs</td>
<td style="text-align: right;">1 564,6 μs</td>
<td style="text-align: right;">42,9688</td>
<td style="text-align: right;">13,6719</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">160,92 KB</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 669,1 μs</td>
<td style="text-align: right;">1 822,5 μs</td>
<td style="text-align: right;">41,0156</td>
<td style="text-align: right;">9,7656</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">145,14 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNet</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">10 642,6 μs</td>
<td style="text-align: right;">11 767,8 μs</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">776,28 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePack</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 945,1 μs</td>
<td style="text-align: right;">2 036,7 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">255,49 KB</td>
</tr>
<tr>
<td>LargeWebApiXml</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 487,6 μs</td>
<td style="text-align: right;">2 522,3 μs</td>
<td style="text-align: right;">128,9063</td>
<td style="text-align: right;">19,5313</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">421,66 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8Json</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 949,1 μs</td>
<td style="text-align: right;">2 098,9 μs</td>
<td style="text-align: right;">82,0313</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">278,19 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNet</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">10 367,4 μs</td>
<td style="text-align: right;">10 987,8 μs</td>
<td style="text-align: right;">203,1250</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">746,2 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePack</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 946,0 μs</td>
<td style="text-align: right;">1 975,3 μs</td>
<td style="text-align: right;">72,2656</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">228,89 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXml</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">2 463,8 μs</td>
<td style="text-align: right;">2 521,2 μs</td>
<td style="text-align: right;">117,1875</td>
<td style="text-align: right;">27,3438</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">392,83 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8Json</td>
<td style="text-align: right;">10</td>
<td style="text-align: right;">1 977,8 μs</td>
<td style="text-align: right;">2 084,4 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">1,9531</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">231,07 KB</td>
</tr>
<tr>
<td>SmallWcfText</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">830,1 μs</td>
<td style="text-align: right;">871,4 μs</td>
<td style="text-align: right;">27,3438</td>
<td style="text-align: right;">3,9063</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">91,15 KB</td>
</tr>
<tr>
<td>SmallWcfWebXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">961,7 μs</td>
<td style="text-align: right;">1 062,7 μs</td>
<td style="text-align: right;">29,2969</td>
<td style="text-align: right;">3,9063</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">94,82 KB</td>
</tr>
<tr>
<td>SmallWcfWebJson</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 188,1 μs</td>
<td style="text-align: right;">1 359,0 μs</td>
<td style="text-align: right;">27,3438</td>
<td style="text-align: right;">3,9063</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">94,06 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNet</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 614,2 μs</td>
<td style="text-align: right;">2 688,8 μs</td>
<td style="text-align: right;">82,0313</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">297 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePack</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 615,7 μs</td>
<td style="text-align: right;">1 762,6 μs</td>
<td style="text-align: right;">39,0625</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">133,62 KB</td>
</tr>
<tr>
<td>SmallWebApiXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 982,7 μs</td>
<td style="text-align: right;">2 020,6 μs</td>
<td style="text-align: right;">78,1250</td>
<td style="text-align: right;">11,7188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">270,55 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8Json</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 816,0 μs</td>
<td style="text-align: right;">1 860,2 μs</td>
<td style="text-align: right;">41,0156</td>
<td style="text-align: right;">7,8125</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">146,35 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNet</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">2 524,8 μs</td>
<td style="text-align: right;">2 608,3 μs</td>
<td style="text-align: right;">82,0313</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">263,7 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePack</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 483,8 μs</td>
<td style="text-align: right;">1 792,1 μs</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">101,22 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 933,5 μs</td>
<td style="text-align: right;">1 960,5 μs</td>
<td style="text-align: right;">74,2188</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">239,64 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8Json</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">1 777,2 μs</td>
<td style="text-align: right;">1 831,4 μs</td>
<td style="text-align: right;">37,1094</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">114,71 KB</td>
</tr>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">9 890,2 μs</td>
<td style="text-align: right;">10 865,7 μs</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">125,0000</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">1 261,97 KB</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">10 136,1 μs</td>
<td style="text-align: right;">11 027,3 μs</td>
<td style="text-align: right;">265,6250</td>
<td style="text-align: right;">140,6250</td>
<td style="text-align: right;">31,2500</td>
<td style="text-align: right;">1 266,48 KB</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">12 813,6 μs</td>
<td style="text-align: right;">13 895,2 μs</td>
<td style="text-align: right;">328,1250</td>
<td style="text-align: right;">171,8750</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">1 417,78 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNet</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">25 425,6 μs</td>
<td style="text-align: right;">26 789,2 μs</td>
<td style="text-align: right;">906,2500</td>
<td style="text-align: right;">468,7500</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">3 588,29 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePack</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">8 834,9 μs</td>
<td style="text-align: right;">9 149,7 μs</td>
<td style="text-align: right;">578,1250</td>
<td style="text-align: right;">328,1250</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">2 465,68 KB</td>
</tr>
<tr>
<td>LargeWebApiXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">16 193,5 μs</td>
<td style="text-align: right;">16 481,3 μs</td>
<td style="text-align: right;">750,0000</td>
<td style="text-align: right;">406,2500</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">2 934,26 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8Json</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">13 787,9 μs</td>
<td style="text-align: right;">14 252,9 μs</td>
<td style="text-align: right;">687,5000</td>
<td style="text-align: right;">437,5000</td>
<td style="text-align: right;">140,6250</td>
<td style="text-align: right;">3 677,67 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNet</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">40 312,9 μs</td>
<td style="text-align: right;">43 191,0 μs</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">3 685,57 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePack</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">8 813,8 μs</td>
<td style="text-align: right;">10 180,6 μs</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">187,5000</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">2 292,35 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXml</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">36 767,1 μs</td>
<td style="text-align: right;">39 825,2 μs</td>
<td style="text-align: right;">562,5000</td>
<td style="text-align: right;">312,5000</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">2 911,21 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8Json</td>
<td style="text-align: right;">100</td>
<td style="text-align: right;">13 961,1 μs</td>
<td style="text-align: right;">14 168,9 μs</td>
<td style="text-align: right;">484,3750</td>
<td style="text-align: right;">343,7500</td>
<td style="text-align: right;">156,2500</td>
<td style="text-align: right;">3 673,72 KB</td>
</tr>
<tr>
<td>SmallWcfText</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">5 299,2 μs</td>
<td style="text-align: right;">6 077,0 μs</td>
<td style="text-align: right;">195,3125</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">646,64 KB</td>
</tr>
<tr>
<td>SmallWcfWebXml</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">5 758,0 μs</td>
<td style="text-align: right;">7 026,5 μs</td>
<td style="text-align: right;">187,5000</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">649,61 KB</td>
</tr>
<tr>
<td>SmallWcfWebJson</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">6 747,8 μs</td>
<td style="text-align: right;">7 438,1 μs</td>
<td style="text-align: right;">195,3125</td>
<td style="text-align: right;">62,5000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">686,42 KB</td>
</tr>
<tr>
<td>SmallWebApiJsonNet</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">9 909,7 μs</td>
<td style="text-align: right;">10 125,5 μs</td>
<td style="text-align: right;">515,6250</td>
<td style="text-align: right;">203,1250</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 625,45 KB</td>
</tr>
<tr>
<td>SmallWebApiMessagePack</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 187,4 μs</td>
<td style="text-align: right;">4 299,4 μs</td>
<td style="text-align: right;">238,2813</td>
<td style="text-align: right;">85,9375</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">768,83 KB</td>
</tr>
<tr>
<td>SmallWebApiXml</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">9 301,4 μs</td>
<td style="text-align: right;">9 428,8 μs</td>
<td style="text-align: right;">484,3750</td>
<td style="text-align: right;">171,8750</td>
<td style="text-align: right;">15,6250</td>
<td style="text-align: right;">1 652,37 KB</td>
</tr>
<tr>
<td>SmallWebApiUtf8Json</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 849,8 μs</td>
<td style="text-align: right;">4 906,9 μs</td>
<td style="text-align: right;">250,0000</td>
<td style="text-align: right;">93,7500</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">841,2 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreJsonNet</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">18 942,1 μs</td>
<td style="text-align: right;">19 746,0 μs</td>
<td style="text-align: right;">406,2500</td>
<td style="text-align: right;">125,0000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 566,54 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreMessagePack</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">3 682,6 μs</td>
<td style="text-align: right;">3 756,6 μs</td>
<td style="text-align: right;">171,8750</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">694,65 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreXml</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">25 635,3 μs</td>
<td style="text-align: right;">26 039,3 μs</td>
<td style="text-align: right;">406,2500</td>
<td style="text-align: right;">125,0000</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">1 627,79 KB</td>
</tr>
<tr>
<td>SmallAspNetCoreUtf8Json</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">4 896,0 μs</td>
<td style="text-align: right;">4 979,0 μs</td>
<td style="text-align: right;">179,6875</td>
<td style="text-align: right;">54,6875</td>
<td style="text-align: right;">-</td>
<td style="text-align: right;">741,7 KB</td>
</tr>
<tr>
<td>LargeWcfText</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">111 347,5 μs</td>
<td style="text-align: right;">113 444,9 μs</td>
<td style="text-align: right;">3 187,5000</td>
<td style="text-align: right;">1 500,0000</td>
<td style="text-align: right;">875,0000</td>
<td style="text-align: right;">26 256,54 KB</td>
</tr>
<tr>
<td>LargeWcfWebXml</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">110 655,4 μs</td>
<td style="text-align: right;">119 280,8 μs</td>
<td style="text-align: right;">3 062,5000</td>
<td style="text-align: right;">1 312,5000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">26 240,84 KB</td>
</tr>
<tr>
<td>LargeWcfWebJson</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">134 667,6 μs</td>
<td style="text-align: right;">144 168,1 μs</td>
<td style="text-align: right;">3 687,5000</td>
<td style="text-align: right;">1 187,5000</td>
<td style="text-align: right;">500,0000</td>
<td style="text-align: right;">19 557,27 KB</td>
</tr>
<tr>
<td>LargeWebApiJsonNet</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">173 466,7 μs</td>
<td style="text-align: right;">201 296,3 μs</td>
<td style="text-align: right;">6 812,5000</td>
<td style="text-align: right;">1 312,5000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">28 508,8 KB</td>
</tr>
<tr>
<td>LargeWebApiMessagePack</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">77 229,8 μs</td>
<td style="text-align: right;">79 016,2 μs</td>
<td style="text-align: right;">5 812,5000</td>
<td style="text-align: right;">1 500,0000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">26 610,34 KB</td>
</tr>
<tr>
<td>LargeWebApiXml</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">155 180,8 μs</td>
<td style="text-align: right;">156 540,0 μs</td>
<td style="text-align: right;">6 937,5000</td>
<td style="text-align: right;">1 562,5000</td>
<td style="text-align: right;">937,5000</td>
<td style="text-align: right;">32 755,87 KB</td>
</tr>
<tr>
<td>LargeWebApiUtf8Json</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">113 577,7 μs</td>
<td style="text-align: right;">115 954,0 μs</td>
<td style="text-align: right;">6 562,5000</td>
<td style="text-align: right;">1 937,5000</td>
<td style="text-align: right;">1 312,5000</td>
<td style="text-align: right;">36 599,17 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreJsonNet</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">364 533,8 μs</td>
<td style="text-align: right;">366 468,4 μs</td>
<td style="text-align: right;">4 562,5000</td>
<td style="text-align: right;">1 250,0000</td>
<td style="text-align: right;">562,5000</td>
<td style="text-align: right;">29 998,47 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreMessagePack</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">75 584,5 μs</td>
<td style="text-align: right;">76 337,7 μs</td>
<td style="text-align: right;">3 125,0000</td>
<td style="text-align: right;">1 312,5000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">24 512,12 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreXml</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">355 358,8 μs</td>
<td style="text-align: right;">359 180,4 μs</td>
<td style="text-align: right;">4 000,0000</td>
<td style="text-align: right;">1 187,5000</td>
<td style="text-align: right;">625,0000</td>
<td style="text-align: right;">29 263,61 KB</td>
</tr>
<tr>
<td>LargeAspNetCoreUtf8Json</td>
<td style="text-align: right;">1000</td>
<td style="text-align: right;">105 684,4 μs</td>
<td style="text-align: right;">107 125,5 μs</td>
<td style="text-align: right;">4 000,0000</td>
<td style="text-align: right;">2 000,0000</td>
<td style="text-align: right;">1 312,5000</td>
<td style="text-align: right;">35 664,96 KB</td>
</tr>
</tbody>
</table>
<div class="graph_options">
    <select id="count" disabled>
        <option value="0">0 items (baseline)</option>
        <option value="1">10 items</option>
        <option value="2" selected>100 items</option>
        <option value="3">1000 items</option>
    </select>
    <select id="metric" disabled>
        <option value="0">Mean response time</option>
        <option value="1">P95 (95th percentile response time)</option>
        <option value="2">Generation 0 collects per 1,000 operations</option>
        <option value="3">Generation 1 collects per 1,000 operations</option>
        <option value="4">Generation 2 collects per 1,000 operations</option>
        <option value="5">Allocated memory per invocation</option>
    </select>
</div>
<div id="chart_div" style="width: 100%; height: 60vh"></div>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>I’ve spent an inordinate amount searching for it, but I cannot find the original thread or comment. You’ll just have to take my word for it.<a href="#fnref:1" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>According to <a href="https://tools.ietf.org/html/rfc7159">RFC 7159</a>, valid encodings for JSON are UTF-8, UTF-16 or UTF-32, with the default being UTF-8.<a href="#fnref:2" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:3">
<p>I did try using RestSharp, but it’s horribly slow.<a href="#fnref:3" class="footnote-back-ref">&#8617;</a></p>
</li>
</ol>
</div>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2018/03/Achtergond-8.1-groot.png" medium="image" /></item><item><title>PSA: Don’t change the assembly name for published NuGet packages</title><description>Somebody published a new version of a NuGet package with a different assembly name. You’ll never guess what happens next.</description><link>https://www.erikheemskerk.nl/psa-dont-change-assembly-name-published-nuget-packages/</link><guid isPermaLink="false">605618814fc9bc45be1c9a76</guid><category>.NET Core</category><category>MSBuild</category><category>C#</category><category>Dependency Injection</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Thu, 01 Mar 2018 07:08:57 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2018-03-01-Achtergrond-7.1.svg.png" alt="PSA: Don&#x2019;t change the assembly name for published NuGet packages" /><p>A while ago I wrote about <a href="https://www.erikheemskerk.nl/transitive-nuget-dependencies-net-core-got-your-back/">transitive dependencies</a> and how, in the current day and age, MSBuild and classic NuGet (using <code>packages.config</code>, colloquially known as ‘Visual Studio 2015 style’) are pretty bad at this, while the new <code>dotnet</code> tooling (using <code>PackageReference</code>, known as ‘Visual Studio 2017 style’) is much better at it.</p>
<blockquote>
<p>TL;DR – Changing the assembly file name for a published NuGet package causes upgraders to experience difficult and frustrating errors.</p>
</blockquote>
<p>Recently, I ran into an issue. We use <a href="https://www.datadoghq.com/">Datadog</a> to record and graph metrics on our systems and environment. They’ve helpfully provided a library to emit metrics from C# applications. They’ve even <a href="https://www.nuget.org/packages/DogStatsD-CSharp-Client/">published it on NuGet</a>! Of course, we don’t want to tie our application code directly to Datadog’s API. One day, somebody might get a bright idea and say that, instead, we should be storing our metrics in Microsoft Application Insights. At that point we don’t want to have to rewrite large parts of our code. So we’ve defined <a href="https://www.erikheemskerk.nl/meaningful-logging-and-metrics/">an interface called <code>Metrics</code></a> and created a private NuGet package that provides an implementation of that interface. If we switch implementations, all we have to do is switch out that library, rewrite some configuration code, and we’re good to go. So far, so good.</p>
<h2 id="upgrade-to-dotnet">Upgrade to <code>dotnet</code></h2>
<p>I happened to be upgrading a project from MSBuild tooling to <code>dotnet</code> tooling. The process for that is pretty much as follows:</p>
<ol>
<li>Open your <code>.csproj</code> file, and replace it with this minimal XML:</li>
</ol>
<pre><code class="language-xml">&lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt; &lt;!-- Or &quot;Microsoft.NET.SDK.Web&quot; for web apps --&gt;
  &lt;PropertyGroup&gt;
    &lt;TargetFramework&gt;net462&lt;/TargetFramework&gt;
  &lt;/PropertyGroup&gt;
&lt;/Project&gt;
</code></pre>
<ol start="2">
<li>Let your IDE reload the project and start inspecting the errors, adding NuGet and project references as you go.</li>
<li>When all the errors are gone, try running it.</li>
<li>Commit your changes.</li>
</ol>
<p>While I was going through the errors, I saw there was a direct dependency on Datadog’s library to be able to configure it. I opened the ‘Manage NuGet Packages’ tool window and installed the <code>DogStatsD-CSharp-Client</code> package. The version installed was <code>3.1.0</code>. A bit later I came across some code that used our <code>Metrics</code> implementation, so I installed our private NuGet package. It had a dependency on version <code>3.0.0</code> of the same <code>DogStatsD-CSharp-Client</code> package. Because we know the <code>dotnet</code> tooling can deal with this version conflict, I didn’t even give it a second thought.</p>
<h2 id="error-time">Error time</h2>
<p>Finally, it was time to run the code. I was presented with a very disturbing error.</p>
<pre><code class="language-plaintext">Unhandled Exception: System.IO.FileNotFoundException: 
Could not load file or assembly 'DogStatsD-CSharp-Client, Version=3.0.0.94, Culture=neutral, PublicKeyToken=null' or one of its dependencies. 
The system cannot find the file specified.
   at OurMetricsAbstraction.Datadog.DatadogMetrics.IncrementCounter(String name, String[] tags)
   at WereGonnaMakeSoMuchMoneyWithThis.Program.Main(String[] args)
</code></pre>
<p>Wait, what?</p>
<p>The runtime cannot load version <code>3.0.0.94</code> of the <code>DogStatsD-CSharp-Client</code> library. I have just upgraded the version, so could that be a problem? It should’t be, because the build system should handle all of this for me. Maybe there’s an issue with the binding redirects. The new build system automatically generates these based on the references and stores them in <code><em>MyAssemblyName</em>.dll.config</code> (or <code>.exe.config</code>, of course).</p>
<p>Lots of binding redirects there, but none for <code>DogStatsD-CSharp-Client</code>.</p>
<h2 id="file-not-found">File not found</h2>
<p>Wait a minute. It’s not a version mismatch. It says <code>The system cannot find the file specified</code>. If it had been a version mismatch, it would have looked like this:</p>
<pre class="language-plaintext"><code class="language-plaintext">System.IO.FileLoadException: Could not load file or assembly 'DogStatsD-CSharp-Client, Version=3.0.0.94, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
<b>The located assembly's manifest definition does not match the assembly reference.</b> (Exception from HRESULT: 0x80131040)</code></pre>
<p>So, alright, definitely a missing file. Let’s look at the ‘bin’ directory. There are hundreds of files there, but, indeed, <code>DogStatsD-CSharp-Client.dll</code> is not one of them. What gives? I have a direct dependency on the package, so it should be there, right? Is there somehow an issue with the NuGet package? If there were, I would expect to get build errors, though.</p>
<h2 id="csi-nuget">CSI NuGet</h2>
<p>I’m using the excellent <a href="https://www.microsoft.com/en-us/store/p/nuget-package-explorer/9wzdncrdmdm3">NuGet Package Explorer</a> to examine the contents of NuGet packages.</p>
<p><img src="/content/images/2018/02/DogStatsD-CSharp-Client-nupkg-3.1.0.png" alt="Contents of DogStatsD-CSharp-Client 3.1.0" /></p>
<p>Well, well, well. The file in the <code>3.1.0</code> version of the NuGet package is called <code>StatsdClient.dll</code>. Just to drive the point home, what does the <code>3.0.0</code> version look like?</p>
<p><img src="/content/images/2018/02/DogStatsD-CSharp-Client-nupkg-3.0.0.png" alt="Contents of DogStatsD-CSharp-Client 3.0.0" /></p>
<p>Right. Our NuGet package implementing the <code>Metrics</code> interface using the Datadog client was compiled against <code>DogStatsD-CSharp-Client</code>, version <code>3.0.0</code>, so it refers to methods in <code>DogStatsD-CSharp-Client.dll</code>.</p>
<p>The hosting application, however, has a direct reference to version <code>3.1.0</code> of the package, so it refers to methods in <code>StatsdClient.dll</code>. Let me show you some IL code to illustrate this.</p>
<p>Let’s take the following C# code:</p>
<pre><code class="language-csharp">StatsdClient.DogStatsd.Configure(new StatsdClient.StatsdConfig());
</code></pre>
<p>In the NuGet package, the IL for that looks like this:</p>
<pre class="language-msil"><code class="language-msil">newobj       instance void ['<b>DogStatsD-CSharp-Client</b>']StatsdClient.StatsdConfig::.ctor()
call         void ['<b>DogStatsD-CSharp-Client</b>']StatsdClient.DogStatsd::Configure(class ['<b>DogStatsD-CSharp-Client</b>']StatsdClient.StatsdConfig)</code></pre>
<p>For those of you not fluent in IL (which includes myself), the second line is saying ‘call a static method <code>Configure</code> from the type <code>StatsdClient.DogStatsd</code>, located in the assembly <code>DogStatsD-CSharp-Client</code>’.</p>
<p>In the hosting application, the IL for the <em>exact same code</em> looks like this:</p>
<pre class="language-msil"><code class="language-msil">newobj       instance void [<b>StatsdClient</b>]StatsdClient.StatsdConfig::.ctor()
call         void [<b>StatsdClient</b>]StatsdClient.DogStatsd::Configure(class [<b>StatsdClient</b>]StatsdClient.StatsdConfig)</code></pre>
<p>The exact same method is being called, but declared as coming from a <em>different assembly</em>.</p>
<p>So let’s recap what’s happening.</p>
<ol>
<li>The NuGet package has a dependency on <code>DogStatsD-CSharp-Client</code>, version <code>3.0.0</code> or, more formally, <code>&gt;= 3.0.0</code>.</li>
<li>The hosting application has a dependency on the same package, but version <code>&gt;= 3.1.0</code>.</li>
<li>The final resolved version of the package is <code>3.1.0</code>, because it satisfies both requirements.</li>
<li>Because of this, the only file that ends up being copied to the output directory of the project is that of version <code>3.1.0</code>, <code>StatsdClient.dll</code>.</li>
<li>At runtime, the code in the NuGet package instructs the runtime to load <code>DogStatsD-CSharp-Client.dll</code>, which doesn’t exist, causing an exception to be thrown.</li>
</ol>
<h2 id="dangerous-subtleties">Dangerous Subtleties</h2>
<p>In this case, because the conflict is located in a NuGet package, the result is a file not being found, which is a pretty visible problem that also manifests itself in a clear way.</p>
<p>If, however, the implementation for the <code>Metrics</code> interface had been a project in the same solution, the issue would have been much more subtle and might never have been discovered.</p>
<p>In my experiments to recreate the issue for this blog post, I initially created exactly that situation. A class library project that references version <code>3.0.0</code> of the Datadog NuGet package, and then a console application project that references version <code>3.1.0</code> and the class library project.</p>
<p><img src="/content/images/2018/02/Visual-Studio-DogStatsD-CSharp-Client-version-conflict.png" alt="File name conflict between versions of DogStatsD-CSharp-Client in the same solution" /></p>
<p>Visual Studio 2017 nicely highlights the different versions, and coincidentally, also the different file names.</p>
<p>In my console application, I call out to the library project, which calls out to <code>DogStatsD-CSharp-Client.dll</code>. It should cause an exception, right? I thought so too, but it doesn’t. Looking in the bin directory, we can see why.</p>
<p><img src="/content/images/2018/02/bin-directory-StatsDClientTest3.png" alt="bin directory of my little experiment" /></p>
<p>MSBuild has a lot more insight into what the library project is referencing, so it dutifully copies <code>DogStatsD-CSharp-Client.dll</code> into the bin directory of the main project. The library’s code is linked to that, so when that code is executed, the runtime loads <code>DogStatsD-CSharp-Client.dll</code> and runs its code. However, when the main project uses code from the NuGet package – to configure the client, for example – the runtime <em>also</em> loads <code>StatsdClient.dll</code> and runs its code.</p>
<p><img src="/content/images/2018/02/Visual-Studio-2017-module-view.png" alt="Visual Studio Module Window with both modules loaded" /></p>
<p>Why is this bad? Well, because of the different assembly names, the runtime considers these to be different <em>modules</em>, which causes all kinds of ‘strange’ behavior.</p>
<p>For example, <code>static</code> members defined in <code>DogStatsD-CSharp-Client.dll</code> are not the same as those in <code>StatsdClient.dll</code>. This means that I can ‘configure’ the client in the main project, but the code in the library project doesn’t see any of that.</p>
<p>Let’s see this in practice. One of the settings that can be configured is the prefix that automatically gets added to metric names. Because this is a private piece of data, I wrote some code that extracts it by <del>hacking</del> reflection.</p>
<pre><code class="language-csharp">private static string GetPrefix()
{
    var serviceField = typeof(DogStatsd).GetField(&quot;_dogStatsdService&quot;, BindingFlags.Static | BindingFlags.NonPublic);
    var service = (DogStatsdService) serviceField.GetValue(null);

    var prefixField = typeof(DogStatsdService).GetField(&quot;_prefix&quot;, BindingFlags.Instance | BindingFlags.NonPublic);
    return (string) prefixField.GetValue(service);
}
</code></pre>
<p>I configured the prefix to be <code>&quot;test&quot;</code> in the main project, and when I run the method from both projects, you can see it doesn’t work as we intended:</p>
<pre><code class="language-plaintext">(Main) Prefix: test
(Library) Prefix: (null)
</code></pre>
<p>Now, this is just a metric prefix, but you can imagine what might happen if you configure, for example, a connection string in this manner.</p>
<p>Dependency injection might also suffer from this problem. As you can see in the code snippet I use to extract the static prefix, there is a type called <code>DogStatsdService</code>. This is actually a public type that is the non-static version of the API. It also implements an interface called <code>IDogStatsd</code>.</p>
<p>I can write a class <code>Foo</code> (because naming is difficult) in my library project that has a dependency on <code>IDogStatsd</code>. In my hosting application, I’ll register <code>DogStatsdService</code> to be the implementation for <code>IDogStatsd</code>. Using SimpleInjector, trying to resolve <code>Foo</code> will result in the following exception:</p>
<pre><code class="language-plaintext">SimpleInjector.ActivationException: 
No registration for type Foo could be found and an implicit registration could not be made.
The constructor of type Foo contains the parameter with name 'service' and type IDogStatsd that is not registered. 
Please ensure IDogStatsd is registered, or change the constructor of Foo. 
Note that there exists a registration for a different type StatsdClient.IDogStatsd while the requested type is StatsdClient.IDogStatsd.
</code></pre>
<p>This is both highly informative and confusing. On the one hand, it tells us that there is no registration for <code>IDogStatsd</code>, which is confusing. On the other hand, it does give us a clue by mentioning that <code>there exists a registration for a different type StatsdClient.IDogStatsd while the requested type is StatsdClient.IDogStatsd</code>.</p>
<p>It still requires a fair bit of investigation to resolve.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The assembly names of a NuGet package are much more than just file names. They’re a contract, really. As with most contracts, changing the names will cause issues. Changing them without informing users is not so nice. Changing them without incrementing the major version of the package, which indicates a change that is not backwards compatible, is very unfriendly.</p>
<p>In general, just don’t change the assembly name. Ever. Stick with the one you used when you first published the package.</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2018-03-01-Achtergrond-7.1.svg.png" medium="image" /></item><item><title>DDD Persistence: Recorded Event-Driven Persistence</title><description>The internet is full of blogs and articles about the mythical DDD repository, but all they offer is an interface. How do you actually implement it?</description><link>https://www.erikheemskerk.nl/ddd-persistence-recorded-event-driven-persistence/</link><guid isPermaLink="false">605618814fc9bc45be1c9a75</guid><category>DDD</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Thu, 30 Nov 2017 06:47:13 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2018/03/2017-11-30-Achtergrond-6.1.jpg" alt="DDD Persistence: Recorded Event-Driven Persistence" /><p>When you decide to implement your business logic by applying <span class="smallcaps"><abbr title="Domain Driven Design">DDD</abbr></span>, one of the things you’ll run into is ‘how do I save my changes?’ The internet is <a href="https://lostechies.com/jimmybogard/2009/09/03/ddd-repository-implementation-patterns/">full</a> of <a href="http://blog.gauffin.org/2013/01/repository-pattern-done-right/">blogs</a> and <a href="http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/">articles</a> about the mythical <i><span class="smallcaps">DDD</span> repository</i>, but all they offer is an interface. How do you actually implement it?</p>
<h2 id="the-interface">The interface</h2>
<p>First, let’s take a look at what the interface for the repository should look like. But, first, what should a repository actually do? Per Martin Fowler’s <a href="https://martinfowler.com/eaaCatalog/repository.html">Patterns of Enterprise Application Architecture Catalog</a>:</p>
<blockquote>
<p>[The repository] mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.</p>
</blockquote>
<p>Alright, so at the very least, we should have a method for accessing the entire collection. And it probably makes sense to be able to single out one item if you already know its unique identifier. Let’s not get into super-generic repositories<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a> and say that our domain models have a unique identifier that is truly unique: a <span class="smallcaps"><abbr title="Globally Unique Identifier">GUID</abbr></span>. Our domain is going to be a pretty simple one: placing and modifying orders.</p>
<pre><code class="language-csharp">public interface IOrderRepository
{
  IEnumerable&lt;Order&gt; GetAll();
  Order GetById(Guid orderId);
}
</code></pre>
<p>Right, that takes care of reading. We should of course also be able to create, update and delete orders. Wait, no, that sounds too much like <span class="smallcaps"><abbr title="Create, Read, Update, Delete">CRUD</abbr></span>, which we don’t like because we’re currently in our Ivory <span class="smallcaps">DDD</span> Tower. We should be able to modify the collection of orders. Much better.</p>
<pre><code class="language-csharp">public interface IOrderRepository
{
  IEnumerable&lt;Order&gt; GetAll();
  Order GetById(Guid orderId);
  void Add(Order order);
  void Update(Order order);
  void Remove(Order order);
}
</code></pre>
<p>We’ve nicely side-stepped the <a href="http://verraes.net/2013/04/crud-is-an-anti-pattern/"><span class="smallcaps">CRUD</span> anti-pattern</a> by using different verbs (mostly) for the repository methods. Great!</p>
<h2 id="yessql">YesSQL</h2>
<p>So, let’s go about implementing this interface. Fine, you’ll say. Just spin up a MongoDB instance, and then implement <code>GetAll</code> by retrieving an entire collection, <code>Get​By​Id</code> by doing <code>.Find​Async(Builders&lt;Order&gt;​.Eq(o =&gt; o.OrderId, orderId)</code>. <code>Add</code>, <code>Update</code> and <code>Remove</code> basically map to <code>Insert​One​Async</code>, <code>Update​One​Async</code>, and <code>Delete​One​Async</code>, respectively.</p>
<figure>
<p><img src="/content/images/2018/01/TheArchitect.jpg" alt="The Architect come down from his Ivory Tower to scorn us" /></p>
<figcaption>Uh oh. It’s The Architect. And he doesn’t look happy.</figcaption>
</figure>
<blockquote>
<p>Whoa there, not so fast. We’re already paying through the nose for this big-ass Oracle cluster that we have just sitting there, waiting to be used. I’d like you to store data in Oracle, if you please. And if you’ll turn to page 257 of the <del>Bible</del> Coding Guidelines as set forth by our Enterprise Architect, you’ll notice that it is not allowed to store documents in a table. We can’t run our daily reports and business intelligence cubes on <span class="smallcaps">JSON</span>! So, a table schema in proper <a href="https://en.wikipedia.org/wiki/Third_normal_form"><span class="smallcaps">3NF</span></a>, please.</p>
</blockquote>
<p>Alright. Oracle it is.</p>
<h2 id="the-order-aggregate">The Order aggregate</h2>
<p>At this point, we should probably take a look at our domain model. What is an order, really? Let’s ask our business analyst.</p>
<blockquote>
<p>Well, this is just an <span class="smallcaps"><abbr title="Minimum Viable Product">MVP</abbr></span>, so an order is really simple. It has some customer and shipping information (primarily the address) and some price stuff. Oh, and order lines. And you should be able to modify an order before you finally send it off to be fulfilled.</p>
</blockquote>
<p>For the sake of the example, let’s not get into a discussion about what an order line is. An order line is simply a product number (a <span class="smallcaps"><abbr title="Stock-Keeping Unit">SKU</abbr></span>), a quantity and a per-item price. So, we have something like this:</p>
<pre><code class="language-csharp">public class Order
{
  public Guid OrderId { get; }
  public Address ShippingAddress { get; }
  public Customer Customer { get; }
  public decimal TotalPriceIncludingVat { get; }
  public decimal TotalPriceExcludingVat { get; }
  public IReadOnlyCollection&lt;OrderLine&gt; OrderLines { get; }
  public bool IsReadyForFulfillment { get; private set; }
}

public class OrderLine
{
  public string Sku { get; }
  public int Quantity { get; private set; }
  public decimal PerItemPriceIncludingVat { get; }
}
</code></pre>
<p>Looks easy enough. What about the operations?</p>
<blockquote>
<p>You should be able to create a new order, obviously, add and remove order lines, update an order line’s quantity, and finally send the order off for fulfillment.</p>
</blockquote>
<p>So, something like this:</p>
<pre><code class="language-csharp">public class Order
{
  // rest of class omitted for brevity
  
  public static Order CreateNew(Address shippingAddress, Customer customer) { /* ... */ }
  public void AddOrderLine(string sku, int quantity, decimal perItemPriceIncludingVat) { /* ... */ }
  public void RemoveOrderLine(string sku) { /* ... */ }
  public void ChangeOrderLineQuantity(string sku, int quantity) { /* ... */ }
  public void Fulfill() { /* ... */ }
}
</code></pre>
<p>Now, the implementations of these operations are omitted for brevity, but you can imagine what they would be like. <code>Change​OrderLine​Quantity</code>, for example, would look up the order line with the provided <span class="smallcaps">SKU</span>, and modify its <code>Quantity</code> property.</p>
<h2 id="persist-all-the-things">Persist all the things!</h2>
<p>Now, how do we persist this to an Oracle database? Let’s first take a look at what the orchestrating code (the ‘use case’) should look like. I specifically want to look at the use case of adding an order line, because that’s where it starts to get interesting.</p>
<pre><code class="language-csharp">public void AddOrderLine(Guid orderId, string sku, int quantity)
{
  var order = _orderRepository.GetById(orderId);
  var perItemPrice = _productRepository.GetBySku(sku).PriceIncludingVat;
  
  order.AddOrderLine(sku, quantity, perItemPrice);
  
  _orderRepository.Update(order);
}
</code></pre>
<p>What about the database schema? Well, it’s pretty straight-forward; there’s an <code>ORDER</code> table that contains the orders themselves, and an <code>ORDERLINE</code> table that contains the order lines. <code>ORDERLINE</code> has a foreign key that references back to the <code>ORDER</code> table, to keep things nice and consistent.</p>
<pre><code class="language-sql">create table &quot;ORDER&quot;
(
    ORDERID raw(16) primary key not null,
    SHIPPINGADDRESS varchar(1000) not null,
    CUSTOMEREMAIL varchar(250) not null,
    READY char not null,
    TOTALPRICEINCVAT number not null
);

create table &quot;ORDERLINE&quot;
(
    ORDERID raw(16) not null,
    SKU varchar(50) not null,
    QUANTITY int not null,
    PRICEINCVAT number not null,
    constraint ORDERLINE_PK primary key (ORDERID, SKU),
    constraint ORDERLINE_ORDER_FK foreign key (ORDERID) references &quot;ORDER&quot; (ORDERID)
);
</code></pre>
<p>Implementing creating and deleting an order is pretty much self-explanatory, so let’s try and implement <code>IOrder​Repository.Update()</code>, which should be more of a challenge. Updating the order itself is fairly easy. We just do something like this:</p>
<pre><code class="language-csharp">public void Update(Order order)
{
  _connection.Execute(
    &quot;update ORDER set &quot; + 
    &quot;SHIPPINGADDRESS = :SHIPPINGADDRESS, &quot; +
    &quot;CUSTOMEREMAIL = :CUSTOMEREMAIL, &quot; +
    &quot;READY = :READY, &quot; +
    &quot;TOTALPRICEINCVAT = :TOTALPRICEINCVAT &quot;
    &quot;where ORDERID = :ORDERID&quot;,
    new
    {
      ShippingAddress = order.ShippingAddress.ToString(),
      CustomerEmail = order.Customer.EmailAddress,
      Ready = order.IsReadyForFulfillment ? 'Y' : 'N',
      TotalPriceIncVat = order.TotalPriceIncludingVat,
      OrderId = order.OrderId.ToByteArray(),
    }
  );
}
</code></pre>
<p>Done. On to the order lines. So... <code>foreach (var line in</code>... in what? How do we know what the state of an order line is? We don’t know which order lines are new or existing, or which of the existing ones are modified or unmodified (or ‘clean’ or ‘dirty’, if you will).</p>
<p>We <em>could</em> just iterate over all of them, see if they already exist in the database, and either <code>insert</code> or <code>update</code> all of them. Yuck. That’s not very elegant at all, and it doesn’t scale well when we have an order with hundreds or thousands of lines, or when there is heavy load on the system.</p>
<p>Another solution would be to sneak some extra properties into the domain model, so that it keeps track of those things itself. So we add <code>Is​New</code> and <code>Is​Modified</code> to the order and order line model. How about deletion? We could introduce an <code>Is​Deleted</code> property on the order, but for order lines this presents a problem; if we use a property to indicate it was deleted, we’d have to be careful not to include those deleted lines in any business logic that involved the current order lines of an order, such as checking if there’s already an order line for a particular <span class="smallcaps">SKU</span>. We could introduce a separate collection of order lines that have been deleted; then we can delete an order line from <code>Order​Lines</code> and add it to <code>Deleted​Order​Lines</code> for the repository. You see how this quickly gets out of hand, and in addition to that, <code>Deleted​Order​Lines</code> or <code>Is​Modified</code> are not things that the business cares about. Let’s try and keep the domain model clean.</p>
<p>How about we have the repository keep track of all the entities and associated objects it returns? That’s what an <span class="smallcaps"><abbr title="Object-Relational Mapping">ORM</abbr></span> framework does. It means that you’ll have to deal with the increased memory usage and processing time it takes to keep and process this additional state. An <span class="smallcaps">ORM</span> usually works by capturing the state of an object before it’s returned to the caller, and when saving comparing the differences. It’s really kind of a brute-force approach. Also, you’ll have to pick, configure, and wire up an <span class="smallcaps">ORM</span> framework, which can be difficult to get <em>right</em>, even with something as ubiquitous as Entity Framework. Or you can write one yourself, which is <em>incredibly</em> difficult to get right.</p>
<p>Let’s try a different approach.</p>
<h2 id="persisting-value-types">Persisting value types</h2>
<p>One of the solutions we have is to modify the domain model a bit so that, instead of returning <code>void</code>, operations return a <em>value object</em> describing the change. We then modify the repository interface to have a single method for each kind of change. Yes, this could work. It also has the benefit of making it very explicitly clear that a new operation is not yet implemented, because the repository interface won’t have a method for it yet. The operations on the domain model would look something like this:</p>
<pre><code class="language-csharp">public class Order
{
  // rest of class omitted for brevity
  
  public static Order CreateNew(Address shippingAddress, Customer customer) { /* ... */ }
  public OrderLineAdded AddOrderLine(string sku, int quantity, decimal perItemPriceIncludingVat) { /* ... */ }
  public OrderLineRemoved RemoveOrderLine(string sku) { /* ... */ }
  public OrderLineQuantityChanged ChangeOrderLineQuantity(string sku, int quantity) { /* ... */ }
  public OrderFulfillmentRequested Fulfill() { /* ... */ }
}
</code></pre>
<p>Note that <code>Create​New</code> still returns an <code>Order</code>, because when creating a new order, there is no ambiguity; <em>everything</em> is new. Also note that the type names of the value objects returned are formulated like <strong>events</strong>: <code>Order​Line​Added</code>, <code>Order​Line​Removed</code>, et cetera. They all describe <em>things that have already happened</em>. You could also think of them as very explicit deltas; differences between the state of an order in two points in time.</p>
<p>Next, let’s modify the repository interface to make this work.</p>
<pre><code class="language-csharp">public interface IOrderRepository
{
  IEnumerable&lt;Order&gt; GetAll();
  Order GetById(Guid orderId);
  void Add(Order order);
  void Store(OrderLineAdded @event);
  void Store(OrderLineRemoved @event);
  void Store(OrderLineQuantityChanged @event);
  void Store(OrderFulfillmentRequested @event);
  void Remove(Order order);
}
</code></pre>
<p>The implementation almost writes itself. For brevity, I won’t spell it out here, but you should be able to imagine what it would look like.</p>
<p>There are some problems, however. The initial version of the interface is ideal for when you want to persist data in a NoSQL database, because you can simply serialize your object graph, write it to the database, and you’re done. The second version of the interface, with the value types, is ideal for when you want to persist data in an <span class="smallcaps">RDBMS</span>, because for each event you run some <span class="smallcaps">SQL</span> queries that get your data in the right state. This is pretty cumbersome to do when your storage mechanism is a NoSQL database. Choosing one interface pattern over the other pretty much locks your application into the associated storage mechanism.</p>
<p>Having to define a new method on the repository interface for each operation that your application supports exposes a flaw that is perpendicular to one of its benefits. The interface is a <strong>contract</strong>, and when you extend the interface by adding a new operation, you’ve broken that contract; all of its implementations now need to be modified, which violates the Open/Closed Principle.</p>
<h2 id="persisting-events">Persisting events</h2>
<p>So, how do we solve this conundrum? Driving persistence based on events seems like a good idea, but how do we avoid the Open/Closed Principle violation? Let’s use that angle as the basis for our solution. Let’s go back to a single ‘save’ method.</p>
<pre><code class="language-csharp">public interface IOrderRepository
{
  IEnumerable&lt;Order&gt; GetAll();
  Order GetById(Guid orderId);
  void Store(IEnumerable&lt;IEvent&gt; events);
}
</code></pre>
<p>At least the interface is now much cleaner. Note that, as well as condensing all of the <code>Store</code> methods into one method, the <code>Add</code> and <code>Remove</code> methods have also been removed. So how do we add a new order? That’s an event! How do we remove an order? Also an event! The <code>Store</code> method accepts a collection of <code>IEvent</code>, which can just be a marker interface to make it explicit what kind of objects we accept.</p>
<p>This design still has some problems, though. Because the interface accepts a collection of events, the use case (or service, or whatever you want to call it) has to collect a list of events as it’s performing operations on the order. That shouldn’t be the use case’s job. Also, what if we decide to use a NoSQL database and just want to store a serialized version of the order?</p>
<p>Let’s refine it once more.</p>
<pre><code class="language-csharp">public interface IOrderRepository
{
  IEnumerable&lt;Order&gt; GetAll();
  Order GetById(Guid orderId);
  void Store(Order order);
}
</code></pre>
<p>That’s better. Now it truly does not matter what kind of persistence mechanism you want to use, and the use case can stay truly ignorant about this fact. But who’s collecting the events? It’s the aggregate root itself. Each operation returns <code>void</code> again and, in addition to modifying the aggregate root’s state, it’s adding an event to a list of events that have not yet been persisted.</p>
<p>What do the internals look like? I’m going to focus on a single use case: changing an order line’s quantity. First, let’s look at the <code>Order</code> and <code>OrderLine</code> classes.</p>
<pre><code class="language-csharp">public class Order
{
  public void ChangeOrderLineQuantity(string sku, int quantity)
  {
    var orderLine = _orderLines.First(l =&gt; l.Sku == sku);
      
    orderLine.ChangeQuantity(quantity);
    
    _eventsPendingPersistence.Add(
      new OrderLineQuantityChanged(
        orderId: OrderId,
        sku: sku,
        quantity: quantity
      )
    );
  }
  
  public IReadOnlyCollection&lt;IEvent&gt; DequeueAllEvents()
  {
    var events = _eventsPendingPersistence.ToList();
    
    _eventsPendingPersistence.Clear();
    return events;
  }
  
  public IReadOnlyCollection&lt;OrderLine&gt; OrderLines =&gt; _orderLines;
  
  private readonly List&lt;OrderLine&gt; _orderLines = new List&lt;OrderLine&gt;();
  private readonly List&lt;IEvent&gt; _eventsPendingPersistence = new List&lt;IEvent&gt;();
}

public class OrderLine
{
  internal void ChangeQuantity(int quantity)
  {
      Quantity = quantity;
  }
  
  public int Quantity { get; private set; }
}
</code></pre>
<p>The <code>Dequeue​All​Events</code> method provides the repository access to the events that have not yet been persisted. It’s called ‘dequeue’ because it returns all the events in the ‘queue’, and then empties the queue. It’s implemented using a <code>List&lt;T&gt;</code> rather than a <code>Queue&lt;T&gt;</code>, because we don’t need the random access, one at a time operations that it exposes.</p>
<p>The use case code is trivially simple. Get the order, operate on it, store it.</p>
<pre><code class="language-csharp">public void ChangeOrderLineQuantity(Guid orderId, string sku, int quantity)
{
  var order = _orderRepository.GetById(orderId);

  order.ChangeOrderLineQuantity(sku, quantity);
  
  _orderRepository.Store(order);
}
</code></pre>
<p>What about the repository code? It gets an order, uses <code>Dequeue​All​Events</code> to get all its events and then dynamically dispatches them.</p>
<pre><code class="language-csharp">class OrderRepository: IOrderRepository
{
  public IEnumerable&lt;Order&gt; GetAll() { /* not implemented for brevity */ }
  public Order GetById(Guid orderId) { /* not implemented for brevity */ }
  
  public void Store(Order order)
  {
    var events = order.DequeueAllEvents();
    
    DispatchAllEvents(events);
  }
  
  private void DispatchAllEvents(IReadOnlyCollection&lt;IEvent&gt; events)
  {
    foreach (var @event in events)
      DispatchEvent(@event);  
  }
  
  private void DispatchEvent(IEvent @event)
  {
    Handle((dynamic) @event);
  }
  
  private void Handle(OrderLineQuantityChanged @event)
  {
    _connection.Execute(
      &quot;update ORDERLINE set &quot; +
      &quot;QUANTITY = :QUANTITY &quot; +
      &quot;where ORDERID = :ORDERID and SKU = :SKU&quot;,
      new
      {
        OrderId = @event.OrderId.ToByteArray(),
        @event.Sku,
        @event.Quantity,
      }
    );
  }
}
</code></pre>
<p>Pretty straight-forward. The <code>Dispatch​Event</code> method does something peculiar: it casts the event to <code>dynamic</code>. This is the most straight-forward approach to dispatching events, and it also happens to be the fastest one.</p>
<h3 id="sidebar-dynamic-performance">Sidebar: <code>dynamic</code> performance</h3>
<p>Let me briefly dig in to that statement. I ran a benchmark using <a href="http://benchmarkdotnet.org/">BenchmarkDotNet</a>, comparing various methods of dispatching events; using <code>dynamic</code>, using reflection, using a lambda, and using pattern matching. The results: of course pattern matching using the <code>switch</code> statement is the fastest option at around 6 <abbr title="nanoseconds - 1 nanosecond is 1 billionth of a second">ns</abbr> per invocation on .NET Core, followed closely by... <code>dynamic</code> at around 12 ns per invocation. Reflection, with a pre-warmed <code>Method​Info</code> cache is a lot slower at around 197 ns per invocation<a id="fnref:2" href="#fn:2" class="footnote-ref"><sup>2</sup></a>. I expected reflection to be ‘slow’, but I was pleasantly surprised at how lightning-quick <code>dynamic</code> is, especially on .NET Core. Mind you, we’re still talking about <em>nanoseconds</em>; 200 nanoseconds is one 5 millionth of a second.</p>
<p>For reference, my benchmark code can be found in <a href="https://gist.github.com/heemskerkerik/0a850919f6467431963f667ae260092f">this Gist</a>. You can easily see that the <code>Dynamic​Dispatch</code> class is by far the smallest and easiest to read, and there is no performance-related reason not to do this.</p>
<h2 id="advantages-disadvantages">Advantages &amp; Disadvantages</h2>
<p>So now that we’ve established the ‘event-driven persistence’ pattern, let’s look at some of its advantages and disadvantages.</p>
<h4 id="advantages">Advantages</h4>
<ul>
<li>It allows for easy implementation of your repository for different persistence mechanisms; document-based, relational database, or <a href="https://www.erikheemskerk.nl/event-sourcing-awesome-powerful-different/">Event Sourcing</a> are all very easy to implement.</li>
<li>Even if you’re not going for Event Sourcing, but you still want to store, broadcast, or publish events in your application, you basically get this for free.</li>
<li>Implementing persistence for a relational database is exceedingly easy, without having to rely on an <span class="smallcaps">ORM</span> framework.</li>
<li>Just <em>one</em> method for persistence. Even creation and deletion can be represented as events, after all.</li>
<li>Compared to the ‘persist value objects from the domain model’ pattern, transaction management can remain inside the persistence layer, rather than having to be managed by the orchestrating code.</li>
<li>Your domain model will remain clean of persistence-related artifacts.</li>
</ul>
<h4 id="disadvantages">Disadvantages</h4>
<ul>
<li>Depending on the complexity of your domain model, you’re going to have to model a lot of events.</li>
<li>Only pattern matching gives you static type safety. On the other hand, even with pattern matching, you’ll want to test whether a particular type of event is supported by your code, so you’ll have to write tests for all of the event types, anyway.</li>
</ul>
<h3 id="sidebar-on-modeling-events">Sidebar: On modeling events</h3>
<p>One important characteristic to keep in mind when modeling the events your domain model will raise is this: events are things that happened in the past, and since you cannot change the past, they <strong>should be immutable</strong>. I’m saying <em>should</em>, but when you implement persistence using Event Sourcing, or when you expose these events to the outside world, that <em>should</em> becomes a <em>must</em>.</p>
<p>What this means is that you must not design an event like this:</p>
<pre><code class="language-csharp">class OrderLineAddedEvent
{
  public OrderLine NewOrderLine { get; }
}
</code></pre>
<p>But this looks immutable, you might say. Yes, but <code>Order​Line</code>, being part of the domain model, is almost definitively mutable, which makes the entire event mutable. It means that by the time you handle or serialize this event, the state of the instance you put in there might have changed from when you created the event. This means you’ve lost some information, which might not be all that bad when all you’re doing is persisting the current state, but when you’re doing Event Sourcing or exposing the event to the outside world, it makes your events lose value.</p>
<p>Instead, design your events like this:</p>
<pre><code class="language-csharp">class OrderLineAddedEvent
{
  public string Sku { get; }
  public int Quantity { get; }
  public decimal PerItemPriceIncludingVat { get; }
}
</code></pre>
<p>Also, think about how <em>granular</em> you want your events to be. Too coarse, like <code>Order​Changed​Event</code> that you raise for <em>every</em> change to an order, and it will be difficult to extract what has changed from the event, which is the whole point of this exercise. Too fine-grained, like <code>Order​Shipping​Address​House​Number​Changed</code>, and you’ll get an explosion of events and corresponding <code>Handle</code> methods and a lot of traffic to the database. You want to capture <em>intent</em>. When somebody changes the house number on the shipping address of an order, what they’re doing is either <strong>correcting</strong> the shipping address or <strong>choosing a different</strong> shipping address. You might not be able to capture the distinction from your UI and use cases, but at the very least it becomes apparent that you’ll only change the address <em>as a whole</em>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Sorry for the very long post, but the subject matter is unfortunately not very simple. This pattern, let’s name it ‘recorded event-driven persistence’, is simple and elegant, and it allows you to persist complex domain models without your persistence code becoming total spaghetti.</p>
<p>If you think it can be improved, let me know. Also, if this is already a named pattern, or if you can think of a better name than ‘recorded event-driven persistence’, let me know!</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>An interface called <code>IRepository&lt;TEntity, TId&gt;</code> with a method called <code>TEntity GetById(TId id)</code><a href="#fnref:1" class="footnote-back-ref">&#8617;</a></p>
</li>
<li id="fn:2">
<p><span class="smallcaps"><abbr title="Your Mileage May Vary">YMMV</abbr></span>; my benchmark machine is running an i7-6700HQ at 2.6 GHz.<a href="#fnref:2" class="footnote-back-ref">&#8617;</a></p>
</li>
</ol>
</div>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2018/03/2017-11-30-Achtergrond-6.1.jpg" medium="image" /></item><item><title>Transitive NuGet dependencies: .NET Core’s got your back</title><description>MSBuild seems to have issues with transitive NuGet dependencies, but after a deep dive into the build logs, it turns out to be more subtle.</description><link>https://www.erikheemskerk.nl/transitive-nuget-dependencies-net-core-got-your-back/</link><guid isPermaLink="false">605618814fc9bc45be1c9a73</guid><category>C#</category><category>MSBuild</category><category>.NET Core</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Fri, 15 Sep 2017 18:31:00 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2017-09-15-Achtergrond-2.1.svg-1.png" alt="Transitive NuGet dependencies: .NET Core&#x2019;s got your back" /><p>The other day, a colleague and I were looking into an issue with one of our solutions’ build pipeline. The main project was a ‘classic’ MSBuild project with a transitive NuGet dependency through another classic MSBuild project. The transitive dependency wasn’t being copied into the main project’s output directory, even though the build was succeeding.</p>
<blockquote>
<p>Note: I’ve fictionalized package names in this blog, because the problem has nothing to do with the authors, and I wouldn’t want their names to be associated with this problem.</p>
</blockquote>
<h2 id="whats-a-transitive-nuget-dependency">What’s a transitive NuGet dependency?</h2>
<p>Let’s first start with a slightly simpler question: what is a transitive dependency?</p>
<p>A transitive dependency is an indirect dependency; you depend on something which itself has another dependency that you <em>don’t</em> depend on.</p>
<p>Let’s say you have projects B, C, and D (I’ll explain why I don’t start with ‘A’ in a bit). When project B has a reference to project C and project C has a reference to project D, but B <em>does not</em> have a reference to D, then D is a transitive dependency of B.</p>
<p><img src="/content/images/2017/09/Diagrammen-Dependencies-1-01.svg" alt="B -&gt; C -&gt; D" /></p>
<p>Now, when you create this exact project structure, there will not be an issue; MSBuild will build project D, then project C, and finally project B. The output directory for project B will contain <code>B.dll</code>, <code>C.dll</code>, and <code>D.dll</code>. So far, so good.</p>
<p>Now let’s say that C is a NuGet package, and so is D. The picture changes slightly, because in when you install a package in a classic MSBuild project, its dependencies will also be directly referenced by B. So B has a direct reference to D. However, there is not a single line of code that actually mentions D, or any of its symbols. If you squint, it is still a transitive dependency.</p>
<p><img src="/content/images/2017/09/Diagrammen-Dependencies-2-01.svg" alt="B -&gt; C -&gt; D" /></p>
<h2 id="whats-the-problem">What’s the problem?</h2>
<p>So far, nothing. When you compile the project, the output directory will still contain <code>B.dll</code>, <code>C.dll</code>, and <code>D.dll</code>, just like when they were still projects.</p>
<p>What I’ve left out, so far, is that, actually, there is another layer of referencing. See, in our situation, B is actually the persistence layer of that particular solution. C is a wrapper library that we’d written around D, which is a commonly used NuGet package. The persistence layer is being referenced by the ‘host’ project, which in our case is a web API. Let’s call that A. Now you see why I started with B.</p>
<p><img src="/content/images/2017/09/Diagrammen-Dependencies-3-01.svg" alt="A -&gt; B -&gt; C -&gt; D" /></p>
<p>Project A has a reference to B, but does not have any reference to the dependencies of B. The problem was that, although <code>C.dll</code> was copied to the output directory for A, <code>D.dll</code> was not.</p>
<h2 id="version-hell">Version hell</h2>
<p>Initially, we thought it was ‘just a bug’ in MSBuild. We’d written a hacky MSBuild task to just recursively copy all of the dependencies from all the projects referenced by the main project. I refused to believe this was a scenario that MSBuild ‘just didn’t support,’ so I started digging.</p>
<p>I built our solution with the verbosity set to ‘Detailed,’ and then I came across a couple of interesting messages. Some details left out for brevity.</p>
<pre><code class="language-plaintext">Project &quot;C:\Project\Project.sln&quot; (1) is building &quot;C:\Project\B.csproj&quot; (4:3) on node 1 (default targets).

  There was a conflict between &quot;D, Version=1.50.2.0, Culture=neutral, PublicKeyToken=null&quot; and &quot;D, Version=1.50.0.0, Culture=neutral, PublicKeyToken=null&quot;.
      &quot;D, Version=1.50.2.0, Culture=neutral, PublicKeyToken=null&quot; was chosen because it was primary and &quot;D, Version=1.50.0.0, Culture=neutral, PublicKeyToken=null&quot; was not.
      References which depend on &quot;D, Version=1.50.2.0, Culture=neutral, PublicKeyToken=null&quot; [C:\Project\packages\D.1.50.2\lib\net451\D.dll].
          C:\Project\packages\D.1.50.2\lib\net451\D.dll
            Project file item includes which caused reference &quot;C:\Project\packages\D.1.50.2\lib\net451\D.dll&quot;.
              D, Version=1.50.2.0, Culture=neutral, processorArchitecture=MSIL
      References which depend on &quot;D, Version=1.50.0.0, Culture=neutral, PublicKeyToken=null&quot; [].
          C:\Project\packages\C.1.0.1\lib\net45\C.dll
            Project file item includes which caused reference &quot;C:\Project\packages\C.1.0.1\lib\net45\C.dll&quot;.
              C, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL
...              
Project &quot;C:\Project\Project.sln&quot; (1) is building &quot;C:\Project\A.csproj&quot; (3) on node 1 (default targets).
...
  Dependency &quot;D, Version=1.50.0.0, Culture=neutral, PublicKeyToken=null&quot;.
      Could not resolve this reference. Could not locate the assembly &quot;D, Version=1.50.0.0, Culture=neutral, PublicKeyToken=null&quot;. Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors.
          For SearchPath &quot;C:\Project\B\bin\Release&quot;.
          Considered &quot;C:\Project\B\bin\Release\D.winmd&quot;, but it didn't exist.
          Considered &quot;C:\Project\B\bin\Release\D.dll&quot;,
  			but its name &quot;D, Version=1.50.2.0, Culture=neutral, PublicKeyToken=null&quot;
  			didn't match the expected name &quot;D, Version=1.50.0.0, Culture=neutral, PublicKeyToken=null&quot;.
      Required by &quot;C:\Project\B\bin\Release\B.dll&quot;.
</code></pre>
<p>The plot thickens. What you see here is that during the build of project B, MSBuild finds a conflict between two versions of D, version 1.50.0.0 and version 1.50.2.0. It ends up picking 1.50.2.0, because <strong>that is a direct dependency</strong>. It then lists in detail that project B depends directly on version 1.50.2.0 of package D, while package C depends on version 1.50.0.0 of package D.</p>
<p><img src="/content/images/2017/09/Diagrammen-Dependencies-4-01.svg" alt="A -&gt; B -&gt; (1.50.2.0) C -&gt; (1.50.0.0)" /></p>
<p>This makes sense; C is a utilities library, and for libraries you usually want to depend on the <em>lowest possible version</em>, to maximize compatibility. However, when you’re writing application code, you typically want to depend on the <em>highest possible version</em>, because that might have fewer bugs. This is pretty much what happened; we’d already added a reference to version 1.50.2.0 of package D to project B, and only later did we add a reference to package C.</p>
<p>A few things stand out from the MSBuild output:</p>
<ul>
<li>MSBuild is saying that package D is what caused the reference to ... itself?</li>
<li><code>If this reference is required by your code, you may get compilation errors.</code> Yet the build still completes successfully?</li>
<li>It’s incorrectly stating that, when building project A, <code>B.dll</code> is what depends on <strong>version 1.50.0.0</strong> of package D. It is actually <code>C.dll</code> that depends on that version.</li>
</ul>
<p>These are not major issues (except for the build completing succesfully), but they might slow down an investigation into why something is not working.</p>
<p>So, to recap: because project B has a direct reference to package D, version 1.50.2.0 of that package gets copied to the output directory for project B. When building project A, MSBuild is not able to resolve the reference, because it only sees that package C depends on version 1.50.0.0 of package D. It does irk me that this kind of stuff does not fail the build.</p>
<p>However, at run-time this will cause exceptions; the runtime will load <code>C.dll</code> and see that it has a reference to version 1.50.0.0 of <code>D.dll</code>. It will attempt to load <code>D.dll</code>, but this is not available, so .NET will throw a <code>File​Not​Found​Exception</code>.</p>
<h2 id="binding-redirects">Binding redirects</h2>
<p>The ‘proper’ way of fixing this issue is to create a binding redirect in the <code>app.config</code> or <code>web.config</code> for project A. This is a pretty cumbersome process, as you have to figure out the exact <em>assembly version</em> of the dependency, which might be different from the <em>package version</em>, and in a lot of cases you also have to copy and paste the public key token. In short, this is not a very pleasant experience. Unfortunately, for classic MSBuild projects, it is the only real solution.</p>
<pre><code class="language-xml">&lt;dependentAssembly&gt;
  &lt;assemblyIdentity name=&quot;D&quot; culture=&quot;neutral&quot; publicKeyToken=&quot;1234567890abcdef&quot; /&gt;
  &lt;bindingRedirect oldVersion=&quot;0.0.0.0-1.50.2.0&quot; newVersion=&quot;1.50.2.0&quot; /&gt;
&lt;/dependentAssembly&gt;
</code></pre>
<h2 id="tree-shaking">Tree-shaking</h2>
<p><img src="/content/images/2017/09/tree-shaking.gif" alt="Shake that thang" />
But wait, we’re not done, yet. I made a remark earlier, which is significant to the problem.</p>
<blockquote>
<p>However, there is not a single line of code that references D.</p>
</blockquote>
<p>If you <em>do</em> add a line of code that references that dependency, then the binding redirect does not appear to be necessary. Apparently, MSBuild does some form of tree-shaking to figure out which dependencies are <em>really</em> required and which ones are only there to satisfy indirect dependencies.</p>
<p>When project B <em>directly</em> uses package D, then MSBuild sees it as a required dependency, and therefore uses the version that project B depends on. Otherwise, it uses the version that package C depends on (even though it reports it incorrectly).</p>
<h2 id="net-core-to-the-rescue">.NET Core to the rescue</h2>
<p>Fortunately, there is an even easier solution: use the .NET Core build system (or ‘the new <code>.csproj</code> format, or <code>dotnet</code>, or whatever you want to call it).</p>
<p>The new <code>.csproj</code> format only requires you to specify the direct reference. In our case, that would be package C.</p>
<pre><code class="language-xml">&lt;PackageReference Include=&quot;C&quot; Version=&quot;1.0.1&quot; /&gt;
</code></pre>
<p>When the project is built, .NET Core will first restore all packages, and figure out that package D is a dependency of package C. The big difference is that it doesn’t require project B to have a direct reference to package D.</p>
<p>If you want to upgrade to a different version of package D, you can do this by simply adding a direct reference to it to project B and specifying the version you need. Direct dependencies take precedence over indirect ones, and when packages are restored, .NET Core will resolve the versions correctly.</p>
<pre><code class="language-xml">&lt;PackageReference Include=&quot;C&quot; Version=&quot;1.0.1&quot; /&gt;
&lt;PackageReference Include=&quot;D&quot; Version=&quot;1.50.2.0&quot; /&gt;
</code></pre>
<p><img src="/content/images/2017/09/TransitiveReferencesDotNetCore.png" alt="" /></p>
<p>From my experiments, it did not seem to be necessary to have any binding redirects. However, when that does turn out to be necessary, you can simply enable some properties to instruct .NET Core to generate the binding redirects for you <em>automatically</em>!</p>
<pre><code class="language-xml">&lt;PropertyGroup&gt;
  &lt;AutoGenerateBindingRedirects&gt;true&lt;/AutoGenerateBindingRedirects&gt;
  &lt;GenerateBindingRedirectsOutputType&gt;true&lt;/GenerateBindingRedirectsOutputType&gt;
&lt;/PropertyGroup&gt;
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Even with NuGet (or maybe especially with NuGet), version hell is still alive and kicking. Do yourself a favor and upgrade to the .NET Core build pipeline. It comes with so many other benefits (such as not having to update your <code>.csproj</code> when you add or remove a file), and this is just an added bonus.</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2017-09-15-Achtergrond-2.1.svg-1.png" medium="image" /></item><item><title>Meaningful Logging and Metrics</title><description>Pretty much any application needs logging and metrics. But how do you write the logging and metrics code so it doesn't obfuscate your business logic?</description><link>https://www.erikheemskerk.nl/meaningful-logging-and-metrics/</link><guid isPermaLink="false">605618814fc9bc45be1c9a70</guid><category>Logging</category><category>Monitoring</category><category>Meaningful Code</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Thu, 22 Jun 2017 11:19:31 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2017-06-22-Achtergrond-5.1.svg.jpg" alt="Meaningful Logging and Metrics" /><p>Pretty much any application needs logging. Except maybe the simplest of applications that do not require user interaction and only do one incredibly specific thing. Or applications that only interact via the console. Pretty much any other application <em>needs</em> logging, though. When things are on fire, you need to be able to see what has happened. Maybe it’s the trigger to let you know that something is on fire.</p>
<p>All too often, though, the code that does the logging looks horrible.</p>
<pre><code class="language-csharp">_logger.Info(&quot;Created the order&quot;);
</code></pre>
<p>That’s just peachy. Which order? For what customer? What does it contain? This does not help me at all when trying to pin down the cause of an issue (or even just the issue itself). This also extends to error reporting, by the way. It’s not very helpful to see a message like this in your logs:</p>
<pre><code class="language-plaintext">2017-05-18T16:42:15Z [Critical] An error occurred: System.InvalidOperationException: Sequence contains no elements
  at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
  at EnterpriseySystem.Common.BetterThanEntityFramework.DoTheQuery[T](MyQueryable`1 query)
  at EnterpriseySystem.DataLayer.EverythingRepository.SaveChanges()
  at EnterpriseySystem.Web.MagicUnitOfWorkFilter.OnActionExecuted(ActionExecutedContext context)
</code></pre>
<p>Now, of course, this is exaggerated a bit. Nevertheless, the point should come across. About the only useful factoids you can get from this message are that ‘something went wrong before, during or after the execution of a query’ and ‘it’s something related to saving changes’. It doesn’t tell you what sort of thing it was saving, what the identity of that thing was, or even what the request <span class="smallcaps">URL</span> was.</p>
<p>So, let’s add some properties to our logging.</p>
<h2 id="properties">Properties</h2>
<p>Properties start to give us some context and can make our lives a lot easier.</p>
<pre><code class="language-plaintext">2017-05-18T16:42:15Z [Critical] An error occurred gathering changes preparing to create an order for customer 376572, containing products [ 2352, 23422, 9482 ]: System.InvalidOperationException: Sequence contains no elements
  at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
  at EnterpriseySystem.Common.BetterThanEntityFramework.DoTheQuery[T](MyQueryable`1 query)
  at EnterpriseySystem.DataLayer.EverythingRepository.SaveChanges()
  at EnterpriseySystem.Web.MagicUnitOfWorkFilter.OnActionExecuted(ActionExecutedContext context)
</code></pre>
<p>Now this is something useful. We know the system is trying to create a new order, and which customer it is for, and which products it contains. We also know it’s in the part of the <span class="smallcaps">ORM</span> that is gathering changes. This allows us to very quickly focus our search for the issue.</p>
<p>However, think about what a dumpster fire the logging code for this will become.</p>
<pre><code class="language-csharp">_logger.Critical(
  ex, 
  &quot;An error occurred gathering changes preparing to create an order &quot; + 
  &quot;for customer {0}, containing products [ {1} ]&quot;,
  customer.CustomerId,
  string.Join(&quot;, &quot;, order.Lines.Select(l =&gt; l.ProductId)));
</code></pre>
<p>And this is if I try to make it look nice. Usually it’ll be one giant line of text and parameters. There is another problem, however. What if, during my troubleshooting, I want to find all messages relating to customer 376572? I can search for <code>customer 376572</code>, but that will not find the following message:</p>
<pre><code class="language-plaintext">2017-05-18T15:32:15Z [Info] Removed address record. AddressId = 8748323, CustomerId = 376572
</code></pre>
<p>Clearly we need a better solution. Enter structured logging.</p>
<h2 id="structured-logging">Structured logging</h2>
<p>Structured logging makes data a first-class citizen of your logged messages, rather than something you have to pull out using regular expressions and analyzers.</p>
<p>Traditionally, a logged message is, at its very core, simply a couple of lines of text. Those lines of text might include a lot of commonly available data, such as a timestamp, a level (Information, Warning, Error, etc.), and other application-specific properties, but it is, for all intents and purposes, just a string.</p>
<p>Structured logging turns a log message into an <em>object</em>, with properties. Let’s take a look at how that same exception message from earlier might look when using structured logging. This is a raw message dump from <a href="https://getseq.net/">Seq</a> (a structured logging back-end).</p>
<pre><code class="language-json">{
    &quot;@t&quot;: &quot;2017-05-26T12:06:09.6427612Z&quot;,
    &quot;@mt&quot;: &quot;An error occurred gathering changes preparing to create an order for customer {CustomerId}, containing products {OrderProducts}&quot;,
    &quot;@m&quot;: &quot;An error occurred gathering changes preparing to create an order for customer 376572, containing products [ 2352, 23422, 9482 ]&quot;,
    &quot;@i&quot;: &quot;9060ab2b&quot;,
    &quot;@l&quot;: &quot;Critical&quot;,
    &quot;@x&quot;: &quot;System.InvalidOperationException: Sequence contains no elements\r\n  at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)\r\n  at EnterpriseySystem.Common.BetterThanEntityFramework.DoTheQuery[T](MyQueryable`1 query)\r\n  at EnterpriseySystem.DataLayer.EverythingRepository.SaveChanges()\r\n  at EnterpriseySystem.Web.MagicUnitOfWorkFilter.OnActionExecuted(ActionExecutedContext context)&quot;,
    &quot;CustomerId&quot;: 376572,
    &quot;OrderProducts&quot;: [
        2352,
        23422,
        9482
    ],
    &quot;Environment&quot;: &quot;Development&quot;,
    &quot;HttpRemoteHost&quot;: &quot;::1&quot;,
    &quot;HttpUri&quot;: &quot;http://myawesomeservice.mycompany.io/order&quot;,
    &quot;HttpMethod&quot;: &quot;POST&quot;,
    &quot;ConnectionId&quot;: &quot;0HL36VOIBEH51&quot;
}
</code></pre>
<p>You can see that the customer ID is not only available in the ‘rendered message’ (the <code>@m</code> property), but also as a separate property called <code>Customer​Id</code>. Because it’s available as a separate piece of data, you can suddenly do a bunch of interesting things with it. For example, if you have a big collection of log messages like this, you could <strong>search</strong> for only those related to customer 376572. Or maybe for messages mentioning product 2352. Or you could search for this specific exception and then <strong>graph</strong> the product count into a histogram, to see if there is any correlation.</p>
<p>Notice there are also properties on the message related to the current execution context, such as <code>Http​Uri</code> and <code>Connection​Id</code>. The better logging frameworks allow you to set ‘ambient’ properties. These can be valid for the lifetime of the application, like the hosting machine name, or only valid for a limited scope, like a <span class="smallcaps">HTTP</span> request. All log messages generated while processing that <span class="smallcaps">HTTP</span> request will have those properties, without you having to explicitly add them to each log message.</p>
<p>Of course you need a specialized logging back-end to be able to do something useful with these properties. You’ll need a searchable document store and a front-end to display messages and render graphs. There are many currently available, but some of the big names right now are <a href="https://www.elastic.co/products">Elastic Stack</a>, <a href="https://getseq.net/">Seq</a> and <a href="https://www.splunk.com/">Splunk</a>.</p>
<h2 id="consistent-property-names">Consistent property names</h2>
<p>Something you’ll quickly run into when using structured logging is the consistency of parameter names. If one piece of code logs the products as <code>Products</code> and another logs them as <code>Order​Products</code>, it’ll become a lot more difficult to reliably use those properties.</p>
<p>You can work around this by putting the property name on a static class that‘s used everywhere. Like so:</p>
<pre><code class="language-csharp">internal static class LogPropertyNames
{
  public const string OrderProducts = &quot;{OrderProducts}&quot;;
  public const string CustomerId = &quot;{CustomerId}&quot;;
}

public void CreateOrder(Order order)
{
  _logger.Information(
    $&quot;Creating an order for customer {LogPropertyNames.CustomerId} &quot; +
    $&quot;with products {LogPropertyNames.OrderProducts}.&quot;,
    order.CustomerId,
    order.ProductIds);
}
</code></pre>
<p>This works pretty nicely. As long as you maintain the convention that all property names must be maintained in the <code>Log​Property​Names</code> class, it’s pretty easy to ensure consistent property names.</p>
<p>Still, it’s quite a bit of code. When you’re just perusing a method, this bit of code <em>shouts</em> at you. And this is just a very basic piece of code. You’ll often have many more properties to log, and sometimes it is a <code>Debug</code> or <code>Trace</code> message. You’ll only want to log those when there is actually a sink or target configured to accept those levels, because logging messages consumes time and memory.</p>
<p>Time to refactor this into a method.</p>
<pre><code class="language-csharp">public void CreateOrder(Order order)
{
  LogCreatingOrder(order);
}

private void LogCreatingOrder(Order order)
{
  if (_logger.IsInformationEnabled)
  {
    _logger.Information(
      $&quot;Creating an order for customer {LogPropertyNames.CustomerId} &quot; +
      $&quot;with products {LogPropertyNames.OrderProducts}.&quot;,
      order.CustomerId,
      order.Products.Select(p =&gt; p.ProductId));
  }
}
</code></pre>
<p>Much better! I think we can improve even further. I don’t really like the fact that your classes will become littered with many methods that are still pretty sizable, just so we can do logging. How about we move those somewhere else?</p>
<h2 id="meaningful-logging">Meaningful Logging</h2>
<pre><code class="language-csharp">internal static class LoggerExtensions
{
  public static void LogCreatingOrder(this ILogger logger, Order order)
  {
    if (logger.IsInformationEnabled)
    {
      logger.Information(
        &quot;Creating an order for customer {CustomerId} &quot; +
        &quot;with products {OrderProducts}.&quot;,
        order.CustomerId,
        order.Products.Select(p =&gt; p.ProductId));
    } 
  }
}

public void CreateOrder(Order order)
{
  _logger.LogCreatingOrder(order);
}
</code></pre>
<p>Now we actually have <strong>strongly typed</strong> and <strong>meaningful</strong> logging methods on our logging interface. Note how I removed the usage of <code>Log​Property​Names</code>; if all your actual logging code is in one file, it’s dead simple to ensure consistency of your property names, so you don’t really need the static class any more. This is starting to look pretty nice!</p>
<h2 id="metrics">Metrics</h2>
<p>Along with logging, most <em>non-interactive</em> applications, like web applications, Windows services, or web <span class="smallcaps">API</span>s also need metrics: raw numeric points of data that give you an insight into how well the application is performing and whether it is healthy. I think it’s relevant to see these as different from logging. Yes, with structured logging you <em>can</em> output your metrics as part of logging and use those to create graphs, but in my experience the tooling of dedicated metrics applications, like <a href="https://www.datadoghq.com/">Datadog</a> or <a href="http://www.librato.com/">Librato</a>, is that much better at managing alerts and just processing large quantities of time-series data, that it warrants treating them as a separate concern.</p>
<p>But oftentimes, logging and metrics go hand in hand. You created a user account? Write a log message and increase a counter. You finished processing a <span class="smallcaps">HTTP</span> request? Write a log message and add the elapsed time to a timer metric. Imaging the mess if you use the ‘old’ style of writing logging code and metrics code in between your other code.</p>
<p>Note how usually log messages and metrics are triggered by ‘events‘: a user was created, an error occurred, an <span class="smallcaps">HTTP</span> request was processed. It would be nice if your code would reflect this. Instead of being explicit and talking about ‘logging this’ and ‘outputting that metric’, we can talk about events that happen. They are not application events or domain events, however.</p>
<h2 id="monitoring-events">Monitoring events</h2>
<p>I’d like to propose the term <strong>monitoring events</strong>, because you usually use the output of the events (such as logging and metrics) to monitor the performance and health of your application. It’s also unambiguously different from the other kinds of events I mentioned.</p>
<p>It is, simply put, an <span class="smallcaps">API</span> for your application code to whatever logging, metrics, alerting, or other monitoring solution you have. In my current solutions, it takes the form of a class, like this:</p>
<pre><code class="language-csharp">public class MonitoringEvents
{
  ILogger Logger { get; }
  IMetrics Metrics { get; }
  ILogContext LogContext { get; }
}
</code></pre>
<p><code>IMetrics</code> and <code>ILog​Context</code> define a number of operations to do with metrics and ambient properties, respectively. <code>ILogger</code> is Serilog’s <code>ILogger</code> interface. The actual methods are defined as extension methods and look like this:</p>
<pre><code class="language-csharp">internal static class MonitoringEventsExtensions
{
  public static void UserCreated(this MonitoringEvents monitoringEvents, User user)
  {
    monitoringEvents.Logger.Information(&quot;Created user {UserId}, named {Username}.&quot;,
                                        user.UserId,
                                        user.Username);
    monitoringEvents.Metrics.IncrementCounter(&quot;users.created&quot;);
  }
}
</code></pre>
<p>In the consuming code, it looks like this:</p>
<pre><code class="language-csharp">void CreateUser()
{
  // do complicated business stuff
  var user = new User();

  MonitoringEvents.UserCreated(user);
}
</code></pre>
<p>In this example, <code>Monitoring​Events</code> looks like it’s a property. That’s because it is. It <em>could</em> of course be a field that gets injected via the constructor, but in conversations with my colleagues we decided that this kind of practice leads to ‘constructor’ bloat; it gets very hard to see what the <em>actual</em> dependencies of a class are, and which are the <em>infrastructural</em> dependencies.</p>
<p>For that reason, we opted to expose our interface using a property which gets set by property injection.</p>
<p>This means your actual business logic code, which is supposed to be the <em>raison d’être</em> for your application, can be <strong>more</strong> about actual business logic, and <strong>less</strong> about <em>cross-cutting concerns</em> like monitoring. What you log and which metrics you keep are decoupled from the business code. I think that’s pretty nice with regards to the Single Responsibility Principle, which is not really about responsibilities but about <a href="https://en.wikipedia.org/wiki/Single_responsibility_principle">reasons for change</a>. If you do decide to change to a different logging framework (but in my humble opinion, at the moment there is nothing better than <a href="https://serilog.net/">Serilog</a>), the only places you have to make changes are in your bootstrapping code, in your interface, and in your extension methods.</p>
<p>I really like this approach. In a lot of applications, logging and monitoring is added as an afterthought, if at all, and because there is usually little perceived business value in logging and metrics, it is treated like a red-headed step-child; its code is often sloppy, ugly, and hard to read. Please, think of the step-child.</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2017-06-22-Achtergrond-5.1.svg.jpg" medium="image" /></item><item><title>C# 7.x and 8.0: Uncertainty and Awesomeness</title><description>After looking at the new features for C# 7.1, let’s look at the things Microsoft is looking at to introduce in later versions of C#, especially C# 8.0.</description><link>https://www.erikheemskerk.nl/c-sharp-7-2-and-8-0-uncertainty-awesomeness/</link><guid isPermaLink="false">605618814fc9bc45be1c9a72</guid><category>C#</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Thu, 15 Jun 2017 08:14:30 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2017-06-15-Achtergrond-4.2.svg.png" alt="C# 7.x and 8.0: Uncertainty and Awesomeness" /><p>Last week I wrote about <a href="https://www.erikheemskerk.nl/c-sharp-7-1-polishing-usability">the new features that Microsoft have planned for the next version of C#</a>, version 7.1. Let’s also take a look at the things they have planned for a bit further out. Strap yourselves in, because this will be a rather long read.</p>
<h2 id="c-7.2-and-7.3">C# 7.2 and 7.3</h2>
<p>The next-up versions of the C# language, 7.2 and 7.3, are less clearly defined than 7.1. C# 7.2 will be focused on enabling you to write low-level code without having to resort to the <code>unsafe</code> keyword. There are some features already defined, like <a href="https://github.com/dotnet/csharplang/blob/master/proposals/readonly-ref.md">read-only references</a>, <a href="https://github.com/dotnet/csharplang/pull/206">blittables</a>, and <a href="https://github.com/dotnet/csharplang/pull/206">ref-like types</a>. For C# 7.3, the picture is even less clear. The roadmap slide in the <a href="https://channel9.msdn.com/Events/Build/2017/B8104">BUILD 2017 presentation</a> only mentions ‘Next steps for pattern matching?’.</p>
<p>The presentation also touches on some of the features they’re thinking about for C# 8.0, so let’s have a look at those. I think they’re pretty exciting, but because this is much further out in terms of planning and releasing, many things are still uncertain. Features might change or might not make it at all.</p>
<h2 id="asynchronous-sequences-c-8.0">Asynchronous sequences (C# 8.0)</h2>
<p>C# 5.0 was all about <code>async</code> and <code>await</code>, as we know. However, one of the scenarios that was left unsupported is enumeration (you know, <code>foreach</code>). In order to use <code>foreach</code> (or the entirety of <span class="smallcaps">LINQ</span>) with the result of an asynchronous method, you have to either retrieve all of the results at once, asynchronously, or be content with the fact that enumeration is not asynchronous.</p>
<p>There is a <a href="https://github.com/dotnet/roslyn/issues/261">proposal</a> to support this in the language. It would look something like this:</p>
<pre><code class="language-csharp">IAsyncEnumerable&lt;SearchResult&gt; results = 
    searchEngine.GetAllResults(query);

foreach await (var result in results) { // ... }
</code></pre>
<p>Now this looks simple enough, but for proper support they would also have to support all of this in <span class="smallcaps">LINQ</span> query operators, which is pretty large body of code. They could probably use a lot of the work from <a href="https://www.nuget.org/packages/System.Interactive/">System.​Interactive</a>, from the <a href="http://reactivex.io">Rx project</a>. There’s not a lot of official documentation there, but <a href="http://community.bartdesmet.net/blogs/bart/archive/tags/Rx/default.aspx">Bart de Smet’s blog</a> has some interesting information.</p>
<p>For a lot of scenarios, like querying a database, your data provider would also have to support this scenario. It will probably be quite some time before third party data providers will start supporting this, if at all. I’m looking at you, Oracle. The official Oracle driver does not, to this day, support asynchronous operations <em>at all</em>. Never mind all the <code>Async</code> methods returning <code>Task</code>; they don’t even support the old <code>Begin</code>/<code>End</code> pattern of asynchronous operations.</p>
<p>Regardless of the third party buy-in this feature needs in order to be really useful in day-to-day code, it is very nice to see a way you can asynchronously stream in a large set of items of an unknown size, like, for example, a Twitter feed. Right now you have to retrieve the feed page by page. This should be an implementation detail of the Twitter client you’re using and it should not be represented in your code. With asynchronous sequences, you can abstract away this detail, which is very nice.</p>
<h2 id="asynchronous-dispose-c-8.0">Asynchronous Dispose (C# 8.0)</h2>
<p>Let’s say you have a desktop application that is directly connecting to a database. Yes, I know it’s 2017, but just go along for the example. You begin a transaction and start doing a lot of work there, all asynchronously so you don’t block the <span class="smallcaps">UI</span>. Of course your transaction is initialized in a <code>using</code> statement, so that when an exception occurs, it is neatly disposed of, which in the case of a database transaction means it’s rolled back.</p>
<p>If your transaction has affected a large number of records, rolling back might take a while. And since <code>Dispose</code> is a synchronous call, this means your user interface will be frozen while this is happening. Yes, you can do something like this:</p>
<pre><code class="language-csharp">IDbTransaction transaction = null;

try
{
    transaction = connection.BeginTransaction();

    // do important work

    transaction.Commit();
}
finally
{
    await Task.Run(() =&gt; transaction.Dispose());
}
</code></pre>
<p>... but that’s basically hacking around a shortcoming in the Disposable pattern. A feature is proposed for, at this point, C# 8.0, which would make this a lot easier.</p>
<pre><code class="language-csharp">using await (var transaction = connection.BeginTransaction())
{
    // do important work

    transaction.Commit();
}
</code></pre>
<p>Again, the usefulness of this feature probably depends a lot on third party buy-in for the <code>IAsync​Disposable</code> interface that will be added.</p>
<h2 id="extension-everything-c-8.0">Extension everything (C# 8.0)</h2>
<p>This is one I’m pretty excited about. You’re already able to write extension methods that extend a class’s functionality without having to modify it. That’s it though. You’re not able to add static methods or properties of any kind.</p>
<p>The <a href="https://github.com/dotnet/roslyn/issues/8127">proposal</a> adds new syntax for defining extensions, which makes it possible for you to add, as the feature suggests, anything to a type. The obvious ones are things like instance properties and static methods and properties, but <a href="https://www.slideshare.net/WindowsDev/build-2017-b8104-the-future-of-c">the slide shown at the presentation</a> also shows a static field. Although the slide does not mention it, the proposal mentions that they would (eventually) be able to support instance fields, using the <a href="https://msdn.microsoft.com/en-us/library/dd287757%28v%3Dvs.110%29.aspx"><code>Conditional​Weak​Table</code></a> class.</p>
<p>Supporting instance fields would mean you would be able to attach a whole new set of features to an existing object, without having to modify it and without the object being able to interfere with that functionality. It sounds nice, but I have some reservations about it. On the one hand, it would be cleaner and easier on the eyes than using composition. On the other hand, like with extension methods, it’s sometimes not always clear what is happening when you’re just looking at the code. You pretty much <em>need</em> an <span class="smallcaps">IDE</span> to be able to figure out that something is an extension. Probably it’s best to use this feature sparingly, only when it really makes sense.</p>
<p>The syntax looks a bit Java-ish (or TypeScript-ish), but remember, it’s not final, so it might still improve.</p>
<pre><code class="language-csharp">extension Enrollee extends Person
{
    // static field
    static Dictionary&lt;Person, Professor&gt; enrollees = 
        new Dictionary&lt;Person, Professor&gt;();

    // instance method
    public void Enroll(Professor supervisor) =&gt;
        enrollees[this] = supervisor;

    // instance property
    public Professor Supervisor =&gt;
        enrollees.TryGetValue(this, out var supervisor) 
            ? supervisor
            : null;

    // static property
    public static ICollection&lt;Person&gt; Students =&gt; enrollees.Keys;

    // instance constructor
    public Person(string name, Professor supervisor)
        : this(name)
    {
        this.Enroll(supervisor);
    }
}
</code></pre>
<p>This is a direct transcription of the slide from the BUILD 2017 talk.</p>
<h2 id="records-c-8.0">Records (C# 8.0)</h2>
<p>I can be very short about this: this is awesome. A record type is nothing more than a collection of fields. You only specify the types and names of the fields, and the compiler will do the tedious work of implementing those the right way. The syntax is very simple.</p>
<pre><code class="language-csharp">class Person(string First, string Last);
</code></pre>
<p>When you see what the compiler expands it to, you’ll see all of the boilerplate you no longer have to implement.</p>
<pre><code class="language-csharp">class Person: IEquatable&lt;Person&gt;
{
    public string First { get; }
    public string Last { get; }

    public Person(string First, string Last)
    {
        this.First = First;
        this.Last = Last;
    }

    public void Deconstruct(out string First, out string Last)
    {
        First = this.First;
        Last = this.Last;
    }

    public bool Equals(Person other) =&gt;
        other != null &amp;&amp; 
        Equals(First, other.First) &amp;&amp; 
        Equals(Last, other.Last);

    public override bool Equals(object other) =&gt;
        (other as Person)?.Equals(this) == true;

    public override int GetHashCode() =&gt;
        (First?.GetHashCode() * 17 + 
         Last?.GetHashCode())
        .GetValueOrDefault();

    public Person With(string First = this.First, 
                       string Last = this.Last) =&gt; 
        new Person(First, Last);
}
</code></pre>
<p>That is about 33 lines of code you <strong>don’t</strong> have to write in order to get a nicely functioning <span class="smallcaps">DTO</span> class. As I often say, the best line of code is the one you don’t have to write.</p>
<h2 id="default-interface-implementations-c-8.0">Default interface implementations (C# 8.0)</h2>
<p>When this feature was introduced, I was pretty skeptical. Why would you ever add implementations to an interface? Why wouldn’t you use an abstract class for that?</p>
<p>The case made in the presentation was a pretty good one, however. Let’s look at a logging library from two perspectives, namely from that of the client application writing log messages and from that of the third party extension library that is implementing a new feature, like logging to <span class="smallcaps">FTP</span>. Yes, I know that is a stupid idea. Just go along.</p>
<p>The full interface looks like this:</p>
<pre><code class="language-csharp">public interface ILogger
{
    void Log(LogLevel level, string message);
    void Log(LogLevel level, string format, params obj[] arguments);
    void Debug(string message);
    void Debug(string format, params obj[] arguments);
    void Information(string message);
    void Information(string format, params obj[] arguments);
    void Warning(string message);
    void Warning(string format, params obj[] arguments);
    void Error(string message);
    void Error(string format, params obj[] arguments);
}
</code></pre>
<p>Now, from the client application’s perspective, the most interesting methods are those with a specific log level, such as <code>Debug</code>, <code>Information</code>, <code>Warning</code>, and <code>Error</code>, and their overloads. Possibly the <code>Log</code> methods are also interesting, but less so.</p>
<p>From the <span class="smallcaps">FTP</span> logging feature’s perspective, the <em>only</em> interesting method is the <code>Log(LogLevel, string)</code> method; all of the other methods are basically convenience overloads of that method.</p>
<p>Currently, because it is an interface, an implementation of <code>ILogger</code> <strong>must</strong> implement all of the methods in the interface. If we add a new method, for example <code>void Error(Exception ex, string format, params object[] arguments)</code>, we have broken the contract. All implementations must now be changed to also implement this method.</p>
<p>With default interface implementations, we can define the implementations of the ‘overloads’ in the interface, so that only the <code>Log(LogLevel, string)</code> method is mandatory to implement. It looks like this (edited for brevity):</p>
<pre><code class="language-csharp">public interface ILogger
{
    void Log(LogLevel level, string message);

    void Log(LogLevel level, string format, params object[] arguments)
    {
        Log(level, string.Format(format, arguments));
    }

    void Debug(string message)
    {
        Log(LogLevel.Debug, message);
    }

    void Debug(string format, params object[] arguments)
    {
        Log(LogLevel.Debug, string.Format(format, arguments));
    }
}
</code></pre>
<p>How this technically works (from what I understand) is that the methods that are actually implemented become <strong>virtual</strong> methods on the interface. An implementation <em>can</em> override them if it so chooses, but it doesn’t have to. The primary reason, then, for default interface implementations is being able to extend an interface <strong>without breaking backwards compatibility</strong>. Pretty good stuff.</p>
<h2 id="nullable-reference-types">Nullable reference types</h2>
<p>In 1965, the concept of <code>null</code> was first introduced in the Algol W language by <a href="https://en.wikipedia.org/wiki/Tony_Hoare">Sir Tony Hoare</a>. He famously described this himself as his ‘billion dollar mistake’ and publicly apologized for it. Whether someone else wouldn’t have introduced <code>null</code> if he hadn’t is debatable, but it is undeniable that <code>null</code> reference errors are the cause of many errors.</p>
<p>It would be great if we could make sure that a parameter or property can never be assigned the value <code>null</code>. While there are some partial solutions in C#, such as using <a href="https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts">Code Contracts</a> or <a href="https://github.com/Fody/Nullguard">Fody NullGuard</a>, the community has been asking for years for a first-class solution to this problem.</p>
<p>Some have suggested being able to mark a type as not nullable, using <code>!</code>. A variable <code>string a</code> would be nullable, but <code>string! a</code> would not be. If you did attempt to assign <code>null</code> to non-nullable variable, or  assign a value from a nullable variable without checking it for <code>null</code>, you would get a compiler error. Now, for variables, the compiler could solve this pretty easily, but for parameters or properties, they would have to find a way to add extra metadata. And there are still cases they couldn’t solve that way, like newing up an array (where all the elements are initially <code>null</code>). They would have to drastically change the .NET type system, which would break a lot of existing code.</p>
<p>So immediately, we are reduced to only generating warnings when doing something that <em>might</em> result in a <code>null</code> reference error. The C# team have, for now, chosen to do the opposite of marking a variable as non-nullable; all reference types will become non-nullable by default, and you can mark the type of a variable as ‘null-safe’ by decorating them with <code>?</code>, similar to nullable value types. Using a non-nullable variable that might be <code>null</code> (because you didn’t check if it was yet) will result in a warning, as will assigning the value of a nullable variable to one that is non-nullable.</p>
<p>What does this look like?</p>
<pre><code class="language-csharp">// bar is nullable because its type is string?
void Foo(string? bar)
{
    string baz = bar; 
    // this will generate a warning because baz is non-nullable 
    // but bar is nullable, and we haven’t checked bar 
    // to not be null
}
</code></pre>
<p>To fix this, we simply have to check for <code>null</code>.</p>
<pre><code class="language-csharp">void Foo(string? bar)
{
    if (bar == null)
        throw new ArgumentNullException(nameof(bar));

    string baz = bar; 
}
</code></pre>
<p>This behavior will be opt-in, because you can be sure this will generate tons of warnings for existing code bases. I think this is a great stride forward in making C# an even more safe language and, for a large part, getting rid of one of the most prolific errors in software.</p>
<h2 id="summary">Summary</h2>
<p>Microsoft is really stepping up the language enhancements, and it is great to see that they are so transparent and open about this. If you have something to say about these changes, you can hop on over to the <a href="https://github.com/dotnet/roslyn">Roslyn site at GitHub</a> and <em>say it</em>! If you make a strong enough case, you might even effect change.</p>
<p>What do you guys think about these new features? Let me know in the comments. For now, that is the final word on this round of new C# features.</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2017-06-15-Achtergrond-4.2.svg.png" medium="image" /></item><item><title>C# 7.1 and Beyond: Polishing Usability</title><description>Microsoft is going into point release with C#. I take a look at discards, async Main, tuple projection initializers, and pattern matching with generics.</description><link>https://www.erikheemskerk.nl/c-sharp-7-1-polishing-usability/</link><guid isPermaLink="false">605618814fc9bc45be1c9a71</guid><category>C#</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Thu, 08 Jun 2017 12:00:17 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2017-06-08-Achtergrond-4.1.svg.png" alt="C# 7.1 and Beyond: Polishing Usability" /><p>Last year I talked about the <a href="https://www.erikheemskerk.nl/csharp-7-0-improvements/">new features of C# 7.0</a>. Visual Studio 2017 has been released a few months ago, so you can now enjoy using these features in your day-to-day work. But Microsoft has, of course, not been idle in the meantime. They’ve detailed what they were, and are, working on in a presentation at BUILD 2017 called <a href="https://channel9.msdn.com/Events/Build/2017/B8104">The Future of C#</a>.</p>
<p>They explain how they will move forward from C# 7.0. Microsoft will actually start doing ‘point releases’ of the C# language and the compiler. This means they can release incremental improvements as updates to Visual Studio and the build tools. Finally, we won’t have to wait until the Microsoft gods favor us and we are blessed with a new major release!</p>
<p>It also means it might get a bit harder to figure out if a particular language feature is supported. Before, you could simply ask ‘are you using Visual Studio <em>(insert year)</em>?’, which would immediately translate into a C# version. Now, you have to find out which specific update of Visual Studio or the build tools is installed. On the other hand, why would you <em>not</em> install an update?</p>
<p>Still, on the whole, I think it’s great that we get these ‘feature updates’ during the lifetime of a Visual Studio release.</p>
<p>All of the features that I’m going to discuss are still in draft; they might not make it at all or only in a later version.</p>
<h2 id="discards">Discards</h2>
<p>Well, except this one. This is a feature that I did not discuss in my previous post, but it’s actually already in C# 7.0. A discard is an indication that you are not interested in using a parameter or declared variable.</p>
<p>Imagine that you have a function that returns a tuple <code>(int count, int sum)</code>. You are only interested in the <code>count</code> field. Without discards, you would have to do something like this:</p>
<pre><code class="language-csharp">(int count, int dummy) = Tally();
</code></pre>
<p>Now, of course, this works, but it’s very impractical. If you have more than one of these in a single the first one will declare the variable <code>dummy</code>, while the second one will only use it.</p>
<pre><code class="language-csharp">(int count, int dummy) = Tally();
(int average, dummy) = GetStatistics();
</code></pre>
<p>Your <span class="smallcaps">IDE</span> will probably complain that the value assigned to <code>dummy</code> is not being used in any execution path. Not very pretty. And it gets even worse when you have several ‘dummies’ of different types. <code>dummy1</code>, <code>dummy2</code>, <code>dummy3</code>…</p>
<p>Discards solve this by basically making <code>_</code> a keyword. When you use <code>_</code>, you’re telling the compiler ‘I don’t care about this, don’t even bother creating a variable for it.’ This was already possible for delegate arguments, but when you wanted to ignore multiple arguments, you (obviously) couldn’t reuse names, so you got something like <code>(_, __, ___)</code>. Ugly. Since discards are a special case, you <em>can</em> reuse them. It’s much prettier and more readable.</p>
<pre><code class="language-csharp">// in deconstruction
(int count, _) = Tally();

// in delegate parameters
var usersWithPosts = 
    context.Users
           .Join(context.Posts,
                 user =&gt; user.UserId,
                 post =&gt; post.UserId,
                 (user, _) =&gt; user);

// in out arguments
int.TryParse(input, out _);

// in pattern matching
switch (input)
{
    case int number:
        Add(number);
        break;
    case object[] _:
        // ignore
        break;
}
</code></pre>
<p>Thank you, Microsoft, for letting us not care about stuff.</p>
<h2 id="async-task-main-c-7.1"><code>async Task Main()</code> (C# 7.1)</h2>
<p>The <code>async</code> and <code>await</code> keywords, introduced in C# 5.0, are an awesome solution to the headache that is asynchronous programming. They make it very easy to write code that calls out to external systems, or does <span class="smallcaps">I/O</span>, without blocking a thread. This makes web applications scalable, and keeps <span class="smallcaps">UI</span> applications responsive.</p>
<p>However, once a part of your code is asynchronous, everything that’s calling into that code also needs to become asynchronous. When your hosting application is a console application or Windows service, this is a bit of a problem. You can write <code>async void Main</code>, but this means that as soon as you hit your first <code>await</code>, the entire application terminates. This is because <code>async void</code> is a fire-and-forget mechanism. Since you cannot return a <code>Task</code> from <code>Main</code>, you will have to use something like <a href="https://github.com/StephenCleary/AsyncEx/wiki/AsyncContext"><code>AsyncContext</code></a> from Stephen Cleary’s excellent <a href="https://github.com/StephenCleary/AsyncEx">AsyncEx library</a>.</p>
<p>Soon, that will no longer be necessary. The compiler and the framework will recognize and properly execute an <code>async Task Main</code> method.</p>
<h2 id="better-null-type-inference-rumored-c-7.1">Better <code>null</code> type inference (rumored - C# 7.1?)</h2>
<p>Have you ever written a ternary expression that returns a nullable value type and one of the branches returns <code>null</code>? It sucks.</p>
<pre><code class="language-csharp">int? x = flag ? 1 : (int?) null;
</code></pre>
<p>Why do I need to specify the type explicitly? If you don’t, you get an error stating that <code>there is no implicit conversion between 'int' and '&lt;null&gt;'</code>. In a future version of C#, you won’t have to specify the type explicitly any more.</p>
<pre><code class="language-csharp">int? x = flag ? 1 : null;
</code></pre>
<p>Much nicer.</p>
<h2 id="default-expression-c-7.1"><code>default</code> expression (C# 7.1<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>)</h2>
<p>This is simply an improvement on something that has been supported <a href="https://msdn.microsoft.com/en-us/library/xwth0h0d%28v=vs.80%29.aspx">since C# 2.0</a> and the advent of generics, back in 2005. It came in the form of <code>default(T)</code>, where <code>T</code> is of course a type. For reference types, it represents <code>null</code>, and for value types such as <code>int</code>, it represents that type’s default, uninitialized, value.</p>
<p>The new feature is that, when the type of <code>T</code> can be inferred, you don’t need to specify it any more.</p>
<p>Instead of writing this:</p>
<pre><code class="language-csharp">int x = default(int);
</code></pre>
<p>... you can write this:</p>
<pre><code class="language-csharp">int x = default;
</code></pre>
<p>Of course this also extends to other usages of <code>default</code>, such as return statements, default values for arguments, and, expanding on the previous point, ternary expressions. This will be valid in a future version of C#:</p>
<pre><code class="language-csharp">var x = flag ? 1 : default;
</code></pre>
<p>That is pretty clean.</p>
<h2 id="tuple-projection-initializers-c-7.1">Tuple projection initializers (C# 7.1)</h2>
<p>I can’t decide if this feature’s name sounds awesome or if it is simply incomprehensible. Anyway, remember tuples from C# 7.0? Let’s say you want to create a tuple <code>(string firstName, string lastName)</code>. And let’s say that you already have local variables called <code>firstName</code> and <code>lastName</code>. Right now, you have to write something like this:</p>
<pre><code class="language-csharp">var t = (firstName: firstName, lastName: lastName);
</code></pre>
<p>This feature infers the names of the tuple elements from their initialization expression, similar to what you can do with anonymous types.</p>
<pre><code class="language-csharp">var t = (firstName, lastName);

Console.WriteLine($&quot;Hello, {t.firstName}!&quot;);
</code></pre>
<p>Simple, and very effective; I like it. One wonders why this wasn’t in there to begin with.</p>
<h2 id="pattern-matching-with-generics-c-7.1">Pattern matching with generics (C# 7.1)</h2>
<p>This is another one of those ‘make a good feature even more useful’ things. The whole point of pattern matching is so instead of this:</p>
<pre><code class="language-csharp">var objString = obj as string;
if (objString != null)
{
    Console.WriteLine($&quot;String: {objString}&quot;);
}
else if (obj is int)
{
    var objInt = (int) obj;
    Console.WriteLine($&quot;Int: {objInt}&quot;);
}
</code></pre>
<p>... you can write this:</p>
<pre><code class="language-csharp">switch (obj)
{
    case string s:
        Console.WriteLine($&quot;String: {s}&quot;);
        break;
    case int i:
        Console.WriteLine($&quot;Int: {i}&quot;);
        break;
}
</code></pre>
<p>There was a limitation in C# 7.0, however. Consider the following code:</p>
<pre><code class="language-csharp">class MyList&lt;T&gt;
{
    public void Add(T item)
    {
        var stringItem = item as string;
        if (stringItem != null)
        {
            Console.WriteLine($&quot;String: {stringItem}&quot;);
        }
    }    
}
</code></pre>
<p>If we use <code>as</code> to determine what kind of item it is, that will compile. However, if we use pattern matching with <code>is</code> (or <code>case</code>), it will not.</p>
<pre><code class="language-csharp">public void Add(T item)
{
    if (item is string s)
    {
        Console.WriteLine($&quot;String: {s}&quot;);
    }
}
</code></pre>
<p>Pattern matching in C# 7.0 has a requirement stating that there must be an explicit or implicit conversion from the left-hand-side type (<code>T</code>) to the right-hand-side type (<code>string</code>). When either is an <em>open type</em>, the compiler determines that this might not be the case. For instance, the closed type for <code>T</code> might be <code>int</code>, and there is obviously no implicit or explicit conversion from <code>int</code> to <code>string</code>. The C# team have determined this is an omission in the spec. In C# 7.1, the spec will be expanded so that either the left-hand-side or the right-hand-side can be an open type.</p>
<h2 id="but-wait-theres-more">But wait, there’s more!</h2>
<p>As said, the feature list for C# 7.1 is not yet definitive, but the things Microsoft have lined up right now look very promising and will be sure to add to the expressiveness of C# and, by doing so, make it just a little bit easier to write readable and maintainable code. And isn’t that what we all want?</p>
<p>What do you think of the improvements that are being made? Let me know in the comments.</p>
<p>Next week, we’ll look at some of the more speculative, but also more radical, improvements that are currently scheduled to be included in <strong>C# 8.0</strong>.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>I initially had this as ‘rumored’, but redditor <a href="https://www.reddit.com/user/grauenwolf">grauenwolf</a> correctly pointed out that the feature is already part of the <a href="https://github.com/dotnet/csharplang/milestone/5">C# 7.1 Candidate Milestone</a>.<a href="#fnref:1" class="footnote-back-ref">&#8617;</a></p>
</li>
</ol>
</div>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2017-06-08-Achtergrond-4.1.svg.png" medium="image" /></item><item><title>Event Sourcing: Eventual Consistency and Responding to Events</title><description>How do you deal with read models being out of sync with the actual system state? In other words, how do you deal with the specter of Eventual Consistency?</description><link>https://www.erikheemskerk.nl/event-sourcing-eventual-consistency-responding-to-events/</link><guid isPermaLink="false">605618814fc9bc45be1c9a6f</guid><category>Event Sourcing</category><category>CQRS</category><category>Messaging</category><category>Eventual Consistency</category><category>Actor Model</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Fri, 03 Mar 2017 05:45:53 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2017-03-03-Achtergrond-3.3.svg.png" alt="Event Sourcing: Eventual Consistency and Responding to Events" /><p>In my last post, about <a href="https://www.erikheemskerk.nl/event-sourcing-cqrs-querying-read-models/"><span class="smallcaps">CQRS</span> and how to keep your read model synchronized</a>, I mentioned how you could ‘broadcast’ your events to other parts of the system in order to keep read-optimized representations of your application’s state in sync with the actual state. You use an ‘event bus’ that will distribute an event to all interested parties.</p>
<p>This event bus can be stupidly simple; it could be a list of every read model in the system and publishing could consist of broadcasting <em>every</em> event to <em>every</em> read model in the system. You could make it a little more intelligent by only distributing an event to a read model that is interested in it. What if updating your read-optimized representation means running some heavy queries against a database and then inserting or updating a ton of records? You don’t want to make your users wait for those updates, right? That’s why it’s often a good idea to make your event bus <strong>asynchronous</strong>. This doesn’t mean the method broadcasting your events returns a <code>Task</code>, it means ‘broadcasting’ the events after you save them is actually putting them on a queue <em>to be broadcast</em>. Your save immediately returns, and the events will get processed in the background.</p>
<h2 id="eventual-consistency">Eventual Consistency</h2>
<blockquote>
<p>If your ‘event bus’ is asynchronous, you will have to come to grips with the realities of Eventual Consistency.</p>
</blockquote>
<p>This is a statement I made in my previous post. What does Eventual Consistency mean? Consider the following: you are writing a blogging application (because at one point or another, we all write a blogging application). One of your requirements is that after the user has finished writing a blog post, he is redirected to the overview of blog posts. However, for whatever reason, it takes a couple of seconds to update the list of blog posts — or maybe just a single second, or several hundreds of milliseconds — which is probably way longer than the time it takes for the user to be redirected and for the application to request a list of blog items from the read model, which is still updating the database.</p>
<p>The result, in the end, is that the user’s blog post is not in the overview. What the hell? The user spends several moments in confusion. Did the blog post get created or not? They press F5. Lo and behold, there’s the blog post.</p>
<p>In the moments after creating the blog post, the system was ‘inconsistent’; that is, not all views of the system (or read models, if you will) were returning the same data. A read model that is eventually consistent will regularly ‘fall out of sync’ with the underlying data, as changes are being processed in the background. Given enough time, it will catch up with all the changes, and it will be consistent again. <strong>Eventually</strong>, it will be <strong>consistent</strong>.</p>
<p>Observing readers will notice I wrote ‘a read model that is eventually consistent’. If you use an asynchronous event bus, then yes, technically your entire application is eventually consistent. However, if updating a read model takes less time than it takes for the user to round-trip a request, then the read model will never appear to be out of sync. For all intents and purposes, it is not eventually consistent but immediately consistent. Hence the nuance: sometimes eventual consistency applies only to several read models, but the rest of the application is not affected.</p>
<h3 id="what-to-do-about-it">What to do about it</h3>
<p>There is a number of things you can do when a view or read model is Eventually Consistent.</p>
<h4 id="ignore-it">Ignore it</h4>
<p>This means making it the user’s problem. In general this will annoy the hell out of users. This <em>might</em> be a perfectly valid solution, depending on the application. If the user’s action was creating a blog post and it doesn’t show up in their list of blog posts, then I’m sure you agree that it’s not a very good solution.</p>
<h4 id="notify-the-user">Notify the user</h4>
<blockquote>
<p>Your blog post has been created and is being processed. It may take several minutes for it to become available.</p>
</blockquote>
<p>This is the ‘fastest’ solution. It requires the least amount of modifications and your application remains responsive. It also requires the user to trust your application. If the read model encounters a problem when processing events, there is, by default, nothing to notify the user. They check after a few minutes, but there is still nothing there.</p>
<p>If this happens often enough, users will stop trusting the application. Which is obviously a bad thing.</p>
<p>If you do go this route, it would probably be a good idea to add a system which will notify users (and possibly administrators) if a read model encounters an error, possibly by sending them an e-mail.</p>
<h4 id="wait-for-the-view-or-read-model-to-become-consistent-again">Wait for the view or read model to become consistent again</h4>
<p>After the blog post has been created, before redirecting the user to the overview, your application can wait, polling the read model every once in a while (probably every hundred milliseconds or so) until the newly created blog post is listed. Only then will you actually redirect the user to their overview. To the user, it appears as if creating the blog post takes longer than it actually does. Another possibility is to wait when you’re requesting the overview of blog posts.</p>
<p>If the event is still in the queue, waiting to be processed because there is a high workload, that might cause your application to wait for an unreasonably long time before the read model is updated.</p>
<h4 id="fake-it">Fake it</h4>
<p>This is not always a possibility, but one way to make an application seem responsive is to ‘fake’ stuff. Of course I’m not talking about actually faking the creation of a blog post. After the blog post has successfully been created, we redirect the user to their blog post overview, supplying it with a parameter that indicates ‘the user just created a blog post with ID such-and-such.’</p>
<p>The overview page will then request a list of blog posts from the read model and check if that blog post is among the results. If it is; great, just do the regular thing and show the results. If it isn’t, get information about the blog post from the system and display it among the results as if it were a regular result. The user will be none the wiser.</p>
<p>Actually, he might be. If the user navigates away and reaches the overview page some other way, chances are he won’t see his newly minted post any more, because the overview page doesn’t receive the ‘newly created post ID’ parameter any more.</p>
<p>You might ask: how does the overview page get information about the new blog post? Doesn’t it need to wait for a read model to become consistent? Why, yes it does. A way to solve this is to create a separate queue for a special read model.</p>
<p>Every event broadcast is put on the ‘regular queue’ and this special queue. The read model could store its information in-memory and contain only the most basic information about pretty much all blog posts ever created. Because it’s in-memory, the queue would never be very long, so the time to become consistent would be virtually zero.</p>
<p>All the posts, though. Hmm, that might still be a lot of information. How about only the most recent <em>x</em> blog posts? Then we don’t even need the passing around of parameters and the view would remain consistent even if you navigated away! You could even tweak how many posts it keeps in memory based on how busy the application is and how long it takes for read models to become consistent! There is a slightly better way, though.</p>
<h4 id="discriminate-against-your-read-models">Discriminate against your read models</h4>
<p>If you have the ability to create and broadcast to multiple queues, why not maintain the normal read models, but separate them into ‘high speed’ (or ‘high priority’) and ‘low speed’ (or, conversely, ‘low priority’)? You could make sure that all read models that need to update in near real-time feed off the high-speed queue, while all the other read models feed off the low-speed queue.</p>
<p>If you have a slow database, you might introduce a cache. The high-speed read models update into and read from the cache. The low-speed versions of those read models write into and read from the database. The cache-backed read models can use the database-backed read models as a fall-back, so that when your application has just started up, you don’t need to replay all the events in the universe to get a working application.</p>
<h4 id="fully-parallel-processing">Fully parallel processing</h4>
<p>Having a low-speed and a high-speed queue solves the issue of having slow read models interfering with near real-time read models. However, it’s still possible for badly behaving read models to interfere with one another.</p>
<p>A couple of great minds have written a manifesto, the <a href="http://www.reactivemanifesto.org/">Reactive Manifesto</a>, which deals with how systems should be designed to make them “more robust, more resilient, more flexible and better positioned to meet modern demands.” One of the characteristics of a reactive system is as follows:</p>
<blockquote>
<p>Failures are contained within each component, isolating components from each other and thereby ensuring that parts of the system can fail and recover without compromising the system as a whole.</p>
</blockquote>
<p>What that means for our read models is that each read model should be as isolated and self-contained as they can be. A way to achieve this is to have each read model have their <strong>own queue</strong> of events. A failure or delay in one read model has very little chance of interfering with other read models. One read model might not be consistent because it is failing, but at least the others are likely to be.</p>
<p>It also decouples the read models on a temporal level. Each read model can process events as fast as possible, so the fastest, in-memory, read models are almost immediately up-to-date, while the slow read models, that update reporting databases, are allowed to lag behind and catch up when there is less load.</p>
<p>By isolating each read model, it is also possible to scale out read models independently. An in-memory read model that merely copies data from events to a state representation probably has little to gain from being scaled out. A read model that does a lot of heavy processing probably has a lot to gain.</p>
<h3 id="beware-of-the-queue">Beware of the queue</h3>
<p>A note about using queues, though. A friend and former colleague of mine would probably have my head on a pike if I didn’t mention <strong>the danger of using queues</strong>. Even though memory and disk space are ‘virtually limitless’, in practice they have very real limits. When messages are being put onto the queue faster than they are pulled off and processed, you will have a problem. Eventually, the queue will be full (because there is no more memory or disk space) and you can’t put new messages onto the queue. What should your application do in that case?</p>
<p>You could discard messages. This is known as load-shedding. For events, it means that, at the very least, some information is missing in some parts of your system. At the very worst, a critical piece of information for a critical business process is missing and your system shits itself and breaks down.</p>
<p>You could also block the write side until there is room on the queue. This is known as back-pressure. At least the system will remain consistent (eventually), but users end up waiting a for a while whenever they <em>do</em> something, possibly getting a time-out.</p>
<p>A much better write-up of this phenomenon is at <a href="http://ferd.ca/queues-don-t-fix-overload.html">ferd.ca</a>.</p>
<h3 id="actor-model">Actor Model</h3>
<p>Independent queues (or mailboxes, if you will), isolation, fault tolerance... Those familiar with the pattern (and that have made it this far) have probably been shouting it for a while now: this is the <strong>Actor Model!</strong></p>
<p>I won’t go into too much detail about the Actor Model, because it is a huge and complex topic. The Actor Model is a pattern that lets you create highly concurrent and fault-tolerant systems. An Actor Model system is comprised of ‘actors’ who are driven by messages and can, in response to a message, only do a limited number of things:</p>
<ul>
<li>Create more actors</li>
<li>Send messages to other actors</li>
<li>Modify their own state</li>
<li>Modify how they will respond to future events</li>
</ul>
<p>An actor only knows how to reach other actors by their <em>address</em>; they don’t have a direct reference to them. The reason for this is <em>location transparency</em>; an actor doesn’t have to know whether another actor lives inside the same process, another process or maybe another machine altogether.</p>
<p>The Actor Model is an insanely powerful way to model your system and achieve resilience and concurrency. It is also calls for a very different way of thinking about and designing your system; even more so than Event Sourcing.</p>
<p>If you want to know more about the Actor Model, I suggest you take a look at <a href="https://petabridge.com/bootcamp/">Petabridge’s Akka.NET Bootcamp</a>. Akka.NET is an Actor Model framework for .NET and it’s a port of the Akka framework for Java.</p>
<h2 id="responding-to-events">Responding to Events</h2>
<p>Eventual Consistency towards the user is one thing, but you can also encounter Eventual Consistency internally. Let’s say you have an event handler that needs to respond to an event <em>X</em> occurring by storing and publishing a different kind of event. To create this new event, you need data from a read model. That read model depends on the data from event <em>X</em> to become fully consistent. The event handler receives the event, but the read model you need to use is not yet up-to-date. What to do?</p>
<p>When you have a fully parallel event bus, where each event handler — which an read model is in its core — has its own queue, the solution is pretty simple. When you receive a message and the read model you depend on doesn’t yet have all the data you need, put the message back on the back of the queue. This is called stashing in the Actor Model. Depending on how busy your system is and how quickly the read model you depend on becomes consistent, you might re-encounter the event several times before you are able to successfully process it. On the other hand, you might not need to make any changes to the read model you depend on and only very little changes to the event handler.</p>
<p>In the case of a (partially) sequential event bus, the problem is much more tedious to solve. You might be able to change the order in which event handlers are invoked, but this is very brittle. You could surround the code that controls the order with large exclamatory comments like <code>/**** DON'T CHANGE THIS CODE OR IT WILL BREAK TEMPORAL COUPLING IN EVENT HANDLERS... YOU PROBABLY WOULDN'T UNDERSTAND ANYWAY ****/</code>. This does not guarantee that someone won’t try and do it, though.</p>
<p>If you’ve implemented low-speed and high-speed queues, you might ensure that the event handler and the read model are on different queues and wait inside the event handler until the read model is consistent, but you will also block everything else on that queue.</p>
<p>You could introduce a new queue specifically for this event handler, but if it blocks until the read model becomes consistent, you might have a pretty low throughput. If the frequency of the event is pretty low, that might be acceptable. When you are able to ‘stash’ events, the throughput will improve. It would require a special case code path, which you have to maintain and test separately.</p>
<p>If your event bus is fully sequential, you might get away with sending an intermediate ‘respond to event <em>X</em>’ message. That way, the read model will definitely have processed event <em>X</em> and will be consistent. However, as we saw, there are a lot of drawbacks to using a fully sequential event bus.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Eventual Consistency is a difficult problem to deal with. Solving it usually requires thinking carefully about how your read models interact with one another and whether you need a read model to update in near real-time or if it’s fine for it to spend some time updating the underlying data store.</p>
<p>If parallel processing makes sense in your case, it’s probably a good idea for each read model to be fully independent by introducing a separate queue for each read model.</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2017-03-03-Achtergrond-3.3.svg.png" medium="image" /></item><item><title>Event Sourcing: CQRS and querying using read models</title><description>How does one query over data in an Event-Sourced system? We examine ways not to and the solution: CQRS. What is it and how do you make it work?</description><link>https://www.erikheemskerk.nl/event-sourcing-cqrs-querying-read-models/</link><guid isPermaLink="false">605618814fc9bc45be1c9a6e</guid><category>Event Sourcing</category><category>CQRS</category><category>Messaging</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Tue, 13 Dec 2016 10:43:31 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2016-12-13-Achtergrond-3.2.svg.png" alt="Event Sourcing: CQRS and querying using read models" /><p>In my previous post I introduced the concept of <a href="https://www.erikheemskerk.nl/event-sourcing-awesome-powerful-different/">Event Sourcing</a>, which is a radically different way of storing application state: instead of storing the current state, we only store the events that lead up to that state. More than one person asked the following question in response: <strong>how do you query your application state?</strong> If you have a number of appointments in your system, how do you get a list of all the appointments on a given day?</p>
<p>I consciously skirted around that question in the previous post, because it was already long and loaded with information. In this post I will attempt to address that question properly.</p>
<h2 id="dont-query-your-events">Don’t query your events</h2>
<p>One of the rules of Event Sourcing is: you don’t query your events. Why not?</p>
<p>First of all, they are hard, if not impossible, to query. If you use an <span class="smallcaps">RDBMS</span> to store your events, you’re most likely serializing events into something like <span class="smallcaps">JSON</span>, so unless your <span class="smallcaps">RBDMS</span> supports a <span class="smallcaps">JSON</span> datatype, it’s incredibly hard to get some data out of them inside of a query, let alone using it to build an index of some sort.</p>
<p>If you use a document database, like RavenDB or DynamoDB, to store your events, this becomes a lot easier, but it’s still not a good idea. Let’s say that you have an <code>Appointment​Created</code> event to indicate that an appointment was created, and an <code>Appointment​Canceled</code> event to indicate it was canceled. Yes, you can ask your database <em>‘give me the information from <code>Appointment​Created</code> events where the date is <strong>X</strong> unless an <code>Appointment​Canceled</code> event exists for that appointment,’</em> which is still somewhat manageable.</p>
<p>What if we add rescheduling into the equation? <em>‘Give me the information from <code>Appointment​Created</code> where the date is <strong>X</strong>, unless an <code>Appointment​Canceled</code> event exists for that appointment, or an <code>Appointment​Rescheduled</code> event exists where the date is no longer <strong>X</strong>.’</em> Wait, what about rescheduling <em>to</em> that date? <em>‘Oh, and include appointments where the <strong>latest</strong> <code>Appointment​Rescheduled</code> event has the date <strong>X</strong>.’</em> You see where I’m going with this, I hope.</p>
<h2 id="dont-query-your-state">Don’t query your state</h2>
<p>You might be tempted to replay all the events in your system in memory and querying over those. This will be slow for large-ish systems and while it <em>might</em> work for very small systems, in theory, it’s still not a very good idea. It requires you to have knowledge of all the streams in your system. While most storage solutions would probably be able to answer this question, it’s usually a rather slow operation. A work-around to that is actually maintaining a list of streams, but this is error-prone (if done manually) and not very elegant.</p>
<h2 id="enter-cqrs">Enter CQRS</h2>
<p><span class="smallcaps">CQRS</span> stands for ‘Command-Query Responsibility Segregation’. It’s a pattern (not an infrastructure, as some people refer to it) coined by <a href="https://goodenoughsoftware.net/">Greg Young</a>. It requires you to have separate classes for writing data and reading data. This then allows you to have separate models for reading and writing. You could write to a set of properly normalized tables and read from a different set of tables that is entirely denormalized. You could write to a relational database and read from a NoSQL database or, in our case, write events and read from whatever tickles your fancy and suits the application.</p>
<p>It does this, in the words of <a href="http://codebetter.com/gregyoung/2010/02/16/cqrs-task-based-uis-event-sourcing-agh/">Greg Young</a>, by ‘creating two objects where there was previously only one.’</p>
<p>Let’s say we have a single repository for our (not Event Sourcing) application that deals with appointments.</p>
<pre><code class="language-csharp">interface IAppointmentRepository
{
    void CreateAppointment(
         int appointmentId, 
         DateTimeOffset time, 
         string title);
    void CancelAppointment(int appointmentId);
    Appointment GetAppointment(int appointmentId);
    IList&lt;Appointment&gt; AllAppointments();
}
</code></pre>
<p>We simply separate this out into two new repositories. One for writing:</p>
<pre><code class="language-csharp">interface IAppointmentWriteRepository
{
    void CreateAppointment(
         int appointmentId, 
         DateTimeOffset time, 
         string title);
    void CancelAppointment(int appointmentId);
}
</code></pre>
<p>And one for reading:</p>
<pre><code class="language-csharp">interface IAppointmentReadRepository
{
    Appointment GetAppointment(int appointmentId);
    IList&lt;Appointment&gt; AllAppointments();    
}
</code></pre>
<p>You should be able to see how this makes it very easy for the implementation of  <code>IAppointment​Write​Repository</code> to use, for example, Event Sourcing, while the implementation of <code>IAppointment​Read​Repository</code> uses, for example, a NoSQL database like DynamoDB.</p>
<p>In this situation we talk about a ‘write model’ and a ‘read model’. The write model is the storage used by the implementation of <code>IAppointment​<b>Write</b>​Repository</code>. The ‘read model’ is then, logically, the storage used by the implementation of <code>IAppointment​<b>Read</b>​Repository</code>. Note that these models <em>can</em> be exactly the same. They don’t have to, however.</p>
<h2 id="command-and-queries">Command and Queries</h2>
<p>The first two letters of <span class="smallcaps">CQRS</span> stand for, respectively, Command and Query. Let’s look at what that means. Greg Young’s definition is as follows:</p>
<blockquote>
<p>A command is any method that mutates state and a query is any method that returns a value.</p>
</blockquote>
<p>This implies that a request to the data store is <em>either</em> a command <em>or</em> a query. Not a little bit of both. This means, by extension, that a command <strong>does not return a value</strong>. It also means that a query <strong>does not mutate state</strong>.</p>
<p>Why is this? One of the reasons behind <span class="smallcaps">CQRS</span> is that it allows your read and write models to be <em>asymmetric</em>. Another very important reason is that it allows commands to become <em>asynchronous</em>. A command might be put onto a queue and executed at a later stage, which means you might have to wait for quite a long while before the command completes.</p>
<p>One of the most common patterns that would compromise this is using auto-generated identifiers. Something like this:</p>
<pre><code class="language-csharp">// returns ID of newly created user
int CreateUser(string userName, string password);
</code></pre>
<p>Usually you need the result of this method, for example to redirect the user to his own profile page after an account was created. You have to wait for the <code>Create​User</code> method to return before you can do anything. If creating the user is done asynchronously, this means the user is sitting there, waiting for something to happen.</p>
<p>In the <span class="smallcaps">CQRS</span> pattern you would fix this by specifying the ID beforehand (using a <span class="smallcaps">GUID</span>, for example). The <code>Create​User</code> method can then return as soon as the request has been validated and it has been put on a queue to be executed. You can then show a page to the user telling them ‘please hold on, we’re processing your request’. Meanwhile you’re polling your read model to see if the account has been created successfully.</p>
<h2 id="synchronization">Synchronization</h2>
<p>If you’re writing to and reading from separate models, how do you go about keeping the data up to date? Let’s assume we’re implementing the <code>IAppointment​Write​Repository</code> from earlier. You could add some code to the end of the <code>Create​Appointment</code> method that calls into another method that updates the read model for you.</p>
<p>That method should really be in another class, to avoid violating the Single Responsibility Principle (which is really the ‘<a href="https://nathanjohnstone.wordpress.com/2016/11/30/the-clean-coder/">Single Reason for Change Principle</a>’). Let’s call it a <code>Normalizer</code>.</p>
<pre><code class="language-csharp">void CreateAppointment(int appointmentId, 
                       DateTimeOffset time, 
                       string title)
{
    var appointment = new Appointment(appointmentId, time, title);
    this.eventStore.Store(appointment.NewEvents);
    this.normalizer.AddAppointment(appointmentId, time, title);
}
</code></pre>
<p>There are a number of problems here. First of all, this violates the Open/Closed Principe. When you add other read models, you might have to change this repository as well. Second of all, the class knows too much about how the <code>Normalizer</code> works; a change to its parameters means this class will also have to change. And what if the logic for creating an appointment changes and we also want to store, for example, which attendees have been invited to which appointments?</p>
<p>All in all, this doesn’t sound like a very scalable and maintainable solution. So what is? The answer is: <strong>events</strong>. We already have events that describe what has happened to an appointment, since they are the source of truth the system is built upon. Why not broadcast those events and have the read models response to those events?</p>
<pre><code class="language-csharp">void CreateAppointment(int appointmentId, 
                       DateTimeOffset time, 
                       string title)
{
    var appointment = new Appointment(appointmentId, time, title);
    this.eventStore.Store(appointment.NewEvents);
    this.eventBus.BroadcastEvents(appointment.NewEvents);
}
</code></pre>
<p>Obviously we need some infrastructure for this, so let’s create a (simplistic) example.</p>
<pre><code class="language-csharp">interface IEventBus
{
    void BroadcastEvents(IEnumerable&lt;object&gt; events);
}

interface IEventHandler&lt;T&gt;
{
    void Handle(T @event);
}
</code></pre>
<p>Then our ‘event bus’ enumerates over all the events and notifies all known handlers for that event type. The implementation would then look something like this:</p>
<pre><code class="language-csharp">class AppointmentNormalizer: 
    IEventHandler&lt;AppointmentCreated&gt;,
    IEventHandler&lt;AppointmentCanceled&gt;,
    IEventHandler&lt;AppointmentRescheduled&gt;
{
    void Handle(AppointmentCreated @event)
    {
        // add a new appointment to your read model
    }

    void Handle(AppointmentCanceled @event)
    {
        // remove an appointment from your read model
    }

    void Handle(AppointmentRescheduled @event)
    {
        // update an appointment's time
    }
}
</code></pre>
<p>This nicely solves most of the problems, except that each method in the implementation of <code>IAppointment​Write​Repository</code> has to store events and then broadcast them. If we combine the two steps into one method, that foregoes a lot of code repetition.</p>
<h2 id="cqrs-and-event-sourcing">CQRS and Event Sourcing</h2>
<p><span class="smallcaps">CQRS</span> on its own is a very powerful pattern, but using it in combination with Event Sourcing is especially potent. Let’s look at a scenario.</p>
<p>We’ve created our little calendar application using Event Sourcing and <span class="smallcaps">CQRS</span>. We’ve created a read model that aggregates appointments by month and stores the results in a NoSQL database. Alas, there is a bug in the code that updates the read model, so it doesn’t handle rescheduling across months. What do we do? Well, we start by <strong>throwing away the entire NoSQL database</strong>. Then we replay all the events in the system to the (<em>bugfixed</em>) read model code, which will rebuild the database from scratch. Problem solved!</p>
<p>This could have probably been solved in a non-Event-Sourcing system by writing a custom piece of code that loops over all the appointments and adjusts the data in the NoSQL database. The beauty of <span class="smallcaps">CQRS</span> with Event Sourcing is: you don’t have to write any custom code! This is part of the normal way the system works!</p>
<h1 id="caveats">Caveats</h1>
<p>There are a number of consequences, depending on your implementation, that you should be aware of when implementing <span class="smallcaps">CQRS</span>.</p>
<p>Your storage space requirements will, relatively, explode. Most storage nowadays is pretty cheap, so this is usually not such a problem. Still, it’s worth being aware of it. The complexity of your codebase will also increase significantly.</p>
<p>If your ‘event bus’ is asynchronous, you will have to come to grips with the realities of Eventual Consistency. In a lot of applications this is not <em>really</em> a big issue, but of all the potential problems to be aware of, this is one of the few that might impact users of your application, so it’s the most important one to be aware of. And, unfortunately, also one of the most mind-boggling ones.</p>
<p>It is important to realize that, like with Event Sourcing, the decision to use <span class="smallcaps">CQRS</span> <em>with separate models</em> (note the emphasis) should not be made on an application-wide level. You should make this decision for each part of your application separately. For instance, it might be valuable to use separate models for appointments, but not for users. If you use separate models anywhere in your application, it’s not a bad idea to use the ‘separate classes for reading and writing’ part of <span class="smallcaps">CQRS</span> in your entire application. If you can, of course.</p>
<h1 id="further-reading">Further reading</h1>
<p>There is a lot of stuff to be found around the web about <span class="smallcaps">CQRS</span> and its many wonderful advantages and dangerous pitfalls. Here is some stuff I found useful.</p>
<ul>
<li><a href="https://lostechies.com/jimmybogard/2012/08/22/busting-some-cqrs-myths/">Jimmy Bogard: Busting some <span class="smallcaps">CQRS</span> myths</a></li>
<li><a href="http://martinfowler.com/bliki/CQRS.html">Martin Fowler: <span class="smallcaps">CQRS</span></a></li>
<li><a href="http://udidahan.com/2010/05/07/cqrs-isnt-the-answer-its-just-one-of-the-questions/">Udi Dahan: <span class="smallcaps">CQRS</span> isn’t the answer - it’s just one of the questions</a></li>
<li><a href="http://udidahan.com/2011/10/02/why-you-should-be-using-cqrs-almost-everywhere/">Udi Dahan: Why you should be using <span class="smallcaps">CQRS</span> almost everywhere...</a></li>
</ul>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2016-12-13-Achtergrond-3.2.svg.png" medium="image" /></item><item><title>Event Sourcing: Awesome, powerful &amp; different</title><description>What is Event Sourcing and what are the benefits that make it a very powerful pattern for data storage? As it turns out, there are many examples of Event Sourcing in daily life.</description><link>https://www.erikheemskerk.nl/event-sourcing-awesome-powerful-different/</link><guid isPermaLink="false">605618814fc9bc45be1c9a6d</guid><category>Event Sourcing</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Mon, 24 Oct 2016 14:55:00 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2016-10-24-Achtergrond-3.1.svg.png" alt="Event Sourcing: Awesome, powerful &amp; different" /><p>I was introduced to Event Sourcing by the <a href="https://msdn.microsoft.com/en-us/library/jj591559.aspx?f=255&amp;MSPPError=-2147217396#"><span class="smallcaps">CQRS</span> Journey</a> series of articles from Microsoft’s Patterns and Practices team. It’s an excellent read about a pattern that offers great flexibility, at a cost. Event Sourcing was mentioned as more of a side note, but I was immediately drawn to its potential. As I’m currently involved in developing an application which uses <span class="smallcaps"><abbr title="Command-Query Responsibility Segregation">CQRS</abbr></span> and Event Sourcing, I’ll be journaling my understanding of it and its benefits and disadvantages.</p>
<h2 id="what-is-event-sourcing">What is Event Sourcing?</h2>
<p>There are several excellent articles about this subject already, so I might be repeating some information. This is <em>my</em> understanding of the paradigm.</p>
<p>The ‘traditional’ way of persisting the state of your application is to  store the state as it currently is. For example, your application might be a calendar, so you want to store appointments. It might look something like this:</p>
<div class="nobreaktable">
<table><thead><tr><th>Appointment ID</th><th>Start time</th><th>End time</th><th>Title</th></thead>
<tbody>
<tr><td style="text-align:right">1</td><td>09:30</td><td>10:45</td><td>Sprint Review</td></tr>
<tr><td style="text-align:right">2</td><td>11:15</td><td>11:30</td><td>Discuss Sprint Review with Stakeholders</td></tr>
</tbody></table>
</div>
<p>This is a table that should look pretty familiar. It might be a table in a relational database, or a set of documents in a key-value store, or even a list of objects held in memory. The point is, it represents the state of system as it is now. Let me ask you a question: how did the system get to this state?</p>
<h3 id="the-audit-log">The audit log</h3>
<p>To answer that question, you could create an audit log. In addition to creating or updating an appointment, you also store a record in the audit log, describing what happened (an ‘event’). For a single appointment, it might look something like this:</p>
<div class="nobreaktable">
<table><thead><tr><th>Sequence</th><th>Event</th></thead>
<tbody>
<tr><td style="text-align:right">1</td><td>Appointment created:
<ul style="margin-bottom:0"><li>09:30 - 10:30</li><li>Title: Review Sprint</li></ul></td></tr>
<tr><td style="text-align:right">2</td><td>Appointment rescheduled: 09:30 - 10:45</td></tr>
<tr><td style="text-align:right">3</td><td>Title changed: Sprint Review</td></tr>
</tbody></table>
</div>
<p>This reveals a whole lot of information that simply isn’t there if you only have the current state of the system. After it was created, the appointment was rescheduled once and its title was changed. The initial end time of 10:30 was there after the appointment was just created, but after it was rescheduled, that information <em>disappeared</em>. Poof.</p>
<p>This kind of information can be pretty useful for users of your application, because they can see what happened and who they should blame. Developers will profit the most from it; it’s so much easier to analyze a problem when you can see <em>why</em> something is in a particular state.</p>
<h3 id="single-source-of-truth">Single source of truth</h3>
<p>An audit log beside your current state is useful, but there’s a problem: conflict. If you find yourself in a situation where your current state says A and your audit log says B, you now have two problems. You not only have to diagnose the problem at hand, but you also have to figure out where the discrepancy comes from.</p>
<p>The problem is the fact that you have two sources of ‘truth’ telling you what the state of the system <em>should</em> be. Your application only looks at the current state, so it sees no problem. You as a developer have two sources of truth. Because they conflict, neither can really be trusted.</p>
<h3 id="enter-event-sourcing">Enter Event Sourcing</h3>
<p>If we eliminate the audit log, we have one source of truth left, but we lose all of the detailed historical information that we value so much. What if we eliminate the current state?</p>
<p>That is the essence of Event Sourcing: instead of storing the current state of a system, you only store the events that lead up to that state. To get the current state, you can ‘replay’ those events in memory.</p>
<p>This current state is only an ‘ephemeral’ representation of events that have happened. It is not persisted. Well, there are snapshots—more on that later—but they are discardable. You can change what events do to the state of your application, but you cannot change the events. They have become the irrefutable truth.</p>
<h3 id="replaying">Replaying?</h3>
<p>To get the current state of your system, you will have to replay all the events. All the events? Well, not <em>really</em> all of them. Your current state is usually divided into several logical ‘objects’, which would traditionally be rows in a table. If you uniquely identify each object, you only need to replay the events for that object to get that object’s state.</p>
<p>Let’s look at a very simplistic example of the current state for our calendar.</p>
<pre><code class="language-csharp">class Appointment
{
    public Guid AppointmentId { get; }
    public DateTime StartTime { get; private set; }
    public DateTime EndTime { get; private set; }
    public string Title { get; private set; }
    public bool IsCanceled { get; private set; }
}
</code></pre>
<p>The events look like this.</p>
<pre><code class="language-csharp">class AppointmentCreated
{
    public Guid AppointmentId { get; }
    public DateTimeOffset StartTime { get; }
    public DateTimeOffset EndTime { get; }
    public string Title { get; }
}

class AppointmentRescheduled
{
    public DateTimeOffset StartTime { get; }
    public DateTimeOffset EndTime { get; }
}

class AppointmentRenamed
{
    public string Title { get; }
}

class AppointmentCanceled
{
}
</code></pre>
<p>Note that the <code>Appointment​Canceled</code> event does not have any properties. Just its type is a meaningful representation of what has happened.</p>
<p>What would replaying those events look like from the point of view of <code>Appointment</code>? Like this.</p>
<pre><code class="language-csharp">void ReplayEvent(AppointmentCreated @event)
{
    AppointmentId = @event.AppointmentId;
    StartTime = @event.StartTime;
    EndTime = @event.EndTime;
    Title = @event.Title;
}

void ReplayEvent(AppointmentRescheduled @event)
{
    StartTime = @event.StartTime;
    EndTime = @event.EndTime;
}

void ReplayEvent(AppointmentRenamed @event)
{
    Title = @event.Title;
}

void ReplayEvent(AppointmentCanceled @event)
{
    IsCanceled = true;
}
</code></pre>
<p>You start with a blank object. Then you simply call <code>Replay​Event</code> for each event that has happened, in the order they have happened. That’s all you need to recreate the current state.</p>
<h2 id="modifying-state">Modifying state</h2>
<p>Since state is represented by the events that have happened in the past, modifying that state is done by appending events. Making this the ‘current state’ object’s responsibility is a convenient way of keeping all the knowledge about the business logic and its rules in one place, which is a philosophy you also see in <span class="smallcaps"><abbr title="Domain Driven Design">DDD</abbr></span>.</p>
<p>The current state object (or entity object or domain object, however you want to call it) also becomes responsible for its own consistency; it is the only actor creating events about itself and it’s able to use events from the past to allow or disallow operations: your appointment has <em>already</em> been canceled; you cannot cancel it again.</p>
<p>This could look something like this.</p>
<pre><code class="language-csharp">class Appointment
{
    public Appointment(
        Guid id,
        DateTimeOffset startTime,
        DateTimeOffset endTime,
        string title)
    {
        if(endTime &lt; startTime)
            throw new EndTimeBeforeStartTimeException();

        AppendEvent(
            new AppointmentCreated(id, startTime, endTime, title)
        );
    }

    public void Reschedule(
        DateTimeOffset startTime,
        DateTimeOffset endTime)
    {
        AppendEvent(
            new AppointmentRescheduled(startTime, endTime)
        );
    }

    public void Cancel()
    {
        if (IsCanceled)
            throw new AppointmentAlreadyCanceledException();

        AppendEvent(
            new AppointmentCanceled()
        );
    }
}
</code></pre>
<p>In this example, <code>Append​Event</code> is a method which will append an event to storage for the unique object that the <code>Appointment</code> refers to.</p>
<p>Validation of the parameters is done inside the methods. It makes a method ultimately responsible for the validity and consistency of the data. For brevity, <code>Reschedule</code> does not validate the parameters, but <code>Cancel</code> does check if the operation is allowed given the current state.</p>
<p>Currently, if you attempt to cancel an appointment that has already been canceled, an exception will be thrown. This doesn’t need to be the case. We could skip the check and just store the event anyway. It does not change the fact it has been canceled; we will still set the <code>Is​Canceled</code> property to <code>true</code> when it is replayed. The point here is: you do not always <em>need</em> to maintain and validate against state. Sometimes it is sufficient to ignore superfluous events.</p>
<p>Notice that all that the constructor and the <code>Reschedule</code> and <code>Cancel</code> methods are actually <em>doing</em> is storing an event. They are not directly modifying the state. Why? Well, we already have a method to modify the state based on an event that has happened: the <code>Replay​Event</code> method. So, in addition to storing an event, <code>Append​Event</code> will also immediately replay the event, so its state is up to date for any other operations you might want to perform and we don’t have to write the code to change state twice.</p>
<h2 id="what-does-it-bring-to-the-table">What does it bring to the table?</h2>
<p>Let’s establish some ground rules about events.</p>
<ul>
<li>Events describe what <em>has already happened</em>, and because we cannot change the past, they are <strong>immutable</strong>. Events cannot be changed after they are persisted.</li>
<li>Extending on the previous rule: they cannot be deleted.</li>
<li>Each event contains all the information needed to represent the state change and to be able to replay it. They should not include computed values if they can be derived from the event, possibly when combined with earlier events.</li>
<li>An event’s metadata contains its type and when it occurred, among others.</li>
<li>Events can be uniquely identified by the identity of the object they apply to and an ordinal—also referred to as the revision or version number.</li>
<li>You do not query over events. They are only used to replay into a representation of the state of the system at a given time.</li>
</ul>
<p>For an event-sourced object, a request to change state should only result in one of three things:</p>
<ul>
<li>Storing an event;</li>
<li>Throwing an exception (because a business rule would be violated);</li>
<li>Doing nothing.</li>
</ul>
<p>Other results, such as directly modifying the object’s state or making a database call, are strongly discouraged because they are side effects. Side effects are usually not represented in the events that are stored, which means you cannot replay them. They also make testing a lot harder.</p>
<p>Considering these rules, let’s look at some obvious, and not so obvious, benefits and disadvantages.</p>
<h3 id="benefits">Benefits</h3>
<p>Because you only have to read and append events, storage is very easy. The only things you need to be able to do are appending an event and retrieving the list of events for a unique object. You can store events pretty much anywhere: in a table, a key-value store, a directory of files, an e-mail, or almost anywhere you can imagine. Since events are immutable, they are very easy to cache, which enables you to get excellent performance.</p>
<p>You will not need <em>explicit</em> transactions any more, because you are only inserting data, not updating or deleting it; you also only need a single table, collection, set, or however your data store groups data. A transaction is like a lock, so not requiring transactions is kind of like lock-free code: there are no deadlocks and it’s <em>much</em> faster. This assumes that you design your events and group them into unique objects in such a way that you never need to manipulate more than one of them at a time—which is not that difficult to achieve.</p>
<p>You also get an audit log for free, which you can use when debugging to see exactly what has happened and why the system is in a particular state.</p>
<p>If you make sure the operations on your entity class are free from side effects, it basically lives in isolation and the class will have a very low <a href="https://en.wikipedia.org/wiki/Efferent_coupling">coupling metric</a>. You’ve isolated that part of the business logic from the rest of the system.</p>
<p>If you’ve achieved this, testing your logic can be expressed as ‘<em>given</em> these events have happened, <em>when</em> this operation is requested, <em>then</em> do these events get stored?’ Or, ‘<em>then</em> does this exception get thrown?’ Given, when, then. Does that sound familiar?</p>
<p>You don’t have to deal with the fact that relational databases are built on set algebra, while your objects are not. Event Sourcing sidesteps the object-relational impedance mismatch.</p>
<p>Your source of truth is a bunch of events in a database. After you store them, you can also <em>broadcast</em> them to the rest of the world via a messaging system. It’s very easy to let other applications know what’s happening in your system. You no longer need to query into another system’s data store and duplicate it to find out what has changed. All you need to store is a list of unique object identifiers and their latest known version number.</p>
<h3 id="disadvantages">Disadvantages</h3>
<p>Of course there are downsides to every approach, and Event Sourcing is no exception. The most important practical disadvantage, one which you will keep encountering when using Event Sourcing, is this: there is no easy way to see what the persisted state of your application is, and you cannot query over it. The only time when you can see what happens is at run-time. A solution, which is outside of the scope of this post, is using a separate model for reading that responds to the events that happen in your system. The <span class="smallcaps"><abbr title="Command-Query Responsibility Segregation">CQRS</abbr></span> pattern combines very well with Event Sourcing to accomplish this.</p>
<p>There are also disadvantages originating from the fact that Event Sourcing is an entirely different approach to what most people are used to. It will take some getting used to. It also takes a bit more boiler-plating than ‘regular’ current-state backing. You need to define each event that can take place in your system, so adding new features might be slower than you’re used to. To offset that, your data becomes tons more valuable.</p>
<p>Event Sourcing sounds like a very inefficient mechanism to store state. It is; it requires a lot more storage than current-state backing. It also requires more processing time, simply because you need to retrieve more data to get to the current state. Because you have practically limitless amounts of storage and processing power nowadays, it’s pretty easy to overcome any problems that arise from these ineffiencies. One of those is the use of snapshots.</p>
<h2 id="snapshots">Snapshots</h2>
<p>You might think that when an event stream contains thousands or tens of thousands of events, your system must become slow, because for each operation it needs to replay all of those events. Well, not necessarily.</p>
<p>Remember, the only thing replaying events is doing is modifying some representation of state. It should be idempotent; whether you replay the events once or a hundred times, it should always have the exact same outcome.</p>
<p>After we’ve replayed, let’s say, ten thousand events, we can take a snapshot of the resulting state and store that, along with the identity of the last replayed event. Now, when we want the current state, we simply load the snapshot and replay any event that has happened since the snapshot, of which there should only be a few. If you’ve created your snapshot correctly, the outcome of loading a snapshot and replaying new events should be <em>identical</em> to replaying all of the events.</p>
<p>It doesn’t invalidate the events that happened. If, for some reason, the representation of state has changed, we can simply discard the snapshot and create a new one by replaying all the events at that point.</p>
<h2 id="testing">Testing</h2>
<p>I mentioned that testing becomes a lot easier. Let’s make this a little more concrete and show <em>how</em> you test an event-sourced object.</p>
<pre><code class="language-csharp">private AppointmentCreated CreateAppointmentCreatedEvent()
{
    return new AppointmentCreated(
        appointmentId: Guid.NewGuid(),
        startTime: DateTimeOffset.Now.AddHours(3),
        endTime: DateTimeOffset.Now.AddHours(4),
        title: &quot;Appointment&quot;
    );
}

private AppointmentRenamed CreateAppointmentRenamedEvent()
{
    return new AppointmentRenamed(title: &quot;Renamed appointment&quot;);
}

private Appointment CreateSut(IEnumerable&lt;IEvent&gt; events)
{
    var sut = new Appointment();
    
    foreach (var @event in events)
        sut.ReplayEvent(@event);
    
    return sut;
}

[Fact]
public void Reschedule_AppendsAppointmentRescheduled()
{
    // GIVEN these events have happened
    var events = new IEvent[]
                 {
                     CreateAppointmentCreatedEvent(),
                     CreateAppointmentRenamedEvent()
                 };
    var sut = CreateSut(events);
    
    // WHEN we ask to reschedule
    var newStartTime = DateTimeOffset.Now.AddHours(5);
    var newEndTime = DateTimeOffset.Now.AddHours(6);
    sut.Reschedule(newStartTime, newEndTime);
    
    // THEN does the AppointmentRescheduledEvent get published?
    sut.AppendedEvents.ShouldAllBeEquivalentTo(
        new[]
        {
            new AppointmentRescheduled(newStartTime, newEndTime)
        },
        config =&gt; config.RespectingRuntimeTypes()
    );
}
</code></pre>
<p>This exposes a few details I haven’t touched upon. They are mostly implementation details; they’re <em>my</em> way of solving some problems, you might choose a different way.</p>
<p>Remember <code>AppendEvent</code>?</p>
<blockquote>
<p><code>Append​Event</code> is a method which will append an event to storage for the unique object that the <code>Appointment</code> refers to.</p>
</blockquote>
<p>That’s a little white lie. From the perspective of the <code>Appointment</code>, that is what happens, but it’s not <em>really</em> what happens.</p>
<p>For testability and decoupling, what really happens is that the event gets appended to a list of ‘unsaved’ events called <code>Appended​Events</code>. This enables us to very easily inspect what an object does and it enables the test you see above.</p>
<p>Note that the test can be divided into three logical sections: <em>given</em> some state, <em>when</em> an action is performed, <em>then</em> can we observe this behavior? A language which makes this much more formal is <strong>Cucumber</strong> (look into it, it’s awesome for behavior testing). Because this post is already getting long in the tooth, you’ll find an example of how to do this in <a href="https://gist.github.com/heemskerkerik/72ee0895c3f88c59a60071c3b61ef072">this Gist</a>.</p>
<h2 id="analogies">Analogies</h2>
<p>Event Sourcing is very powerful way of thinking, but it is not really new. There are event-sourcing systems most of us are familiar with and maybe don’t recognize them as such. Some examples:</p>
<ul>
<li>Your bank account is <em>probably</em> stored using Event Sourcing. The current account balance is simply a projection of all the deposits and withdrawals that you made.</li>
<li>Version control systems. Each commit or change to a file is an event. If you replay all of the events, you get the current state of the source code.</li>
<li>Most large <span class="smallcaps"><abbr title="Relational Database Management System">RDBMS</abbr></span>es use Event Sourcing under the hood. There are, simply put, only three events: insert, update and delete. The <span class="smallcaps">RDBMS</span> stores the events in the transaction log and then applies them to the tables.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>This is my understanding of Event Sourcing. I think it’s great, it’s powerful, but I might be biased because I’m already actively using it in an application under development. I hope you can see its potential. If so, please check out these excellent posts about the subject:</p>
<ul>
<li><a href="https://abdullin.com/post/event-sourcing-why/">Rinat Abdullin: Why Event Sourcing?</a></li>
<li><a href="http://blog.jonathanoliver.com/aggregate-roots-and-shared-data/">Jonathan Oliver: Aggregate Roots and Shared Data</a></li>
<li><a href="https://abdullin.com/post/event-sourcing-versioning/">Rinat Abdullin: Event Sourcing - Versioning</a></li>
<li><a href="http://codebetter.com/gregyoung/2010/02/13/cqrs-and-event-sourcing/">Greg Young: <span class="smallcaps">CQRS</span> and Event Sourcing</a></li>
<li><a href="http://martinfowler.com/eaaDev/EventSourcing.html">Martin Fowler: Event Sourcing</a></li>
</ul>
<p>In future posts I’ll be writing about my experience creating an application using Event Sourcing and <span class="smallcaps">CQRS</span>. Have any other advantages or disadvantages? Let me know!</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2016-10-24-Achtergrond-3.1.svg.png" medium="image" /></item><item><title>Inspecting aspects and interception in .NET, part 3</title><description>A look at the relative performance of various dependency injection (DI) and interception frameworks. I benchmark Unity, SimpleInjector, Autofac and Ninject.</description><link>https://www.erikheemskerk.nl/inspecting-aspects-interception-3/</link><guid isPermaLink="false">605618814fc9bc45be1c9a6c</guid><category>Dependency Injection</category><category>Benchmarking</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Mon, 19 Sep 2016 18:01:54 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2016-09-19-Achtergrond-1.3.svg.png" alt="Inspecting aspects and interception in .NET, part 3" /><p>Previously in this series, I looked at ways we can instrument our code with two of the most basic metrics; <a href="https://www.erikheemskerk.nl/inspecting-aspects-interception/">how often does our code run and how long does it take to execute</a>? After settling on interception, we took a small detour and looked at <a href="https://www.erikheemskerk.nl/inspecting-aspects-interception-2/">how hard it is to measure the time it takes to measure how long it takes to execute your code</a>. This time, we’re going to combine all of them and throw another component in the mix: <span class="smallcaps"><abbr title="Dependency Injection">DI</abbr></span> frameworks.</p>
<h2 id="di-abridged">DI, abridged</h2>
<p>Most of you probably already know what <span class="smallcaps">DI</span> stands for and what dependency injection is. Just a small recap, which is also relevant for the article.</p>
<blockquote>
<p>Dependency Injection is about decoupling a class from its dependencies. Instead of requiring a class to construct its own dependencies, it is passed them at construction time. This promotes readability, testability and, as a result of those, maintainability.</p>
</blockquote>
<p>A consequence of using a <span class="smallcaps">DI</span> framework is that it’s involved in the creation and destruction of most objects in your application. It needs to know, for each class it constructs, which dependencies it requires and how to construct those classes in turn. If you think about the amount of work potentially involved, you quickly come to a realization: it had better be fast.</p>
<h2 id="scope">Scope</h2>
<p>A lot of <span class="smallcaps">DI</span> frameworks are more flexible than most yoga instructors. Some, less so. As such, it’s impractical and unfair to compare all of the possibilities, and it would make the results very hard to interpret. So let’s define what we’ll be benchmarking.</p>
<ul>
<li>Resolving (or creating) an instance of <code>Something</code>, which implements <code>ISomething</code>, in such a way that calls to <code>ISomething.Foo</code> are timed, in the most common way for the framework in question;</li>
<li>Invoking <code>ISomething.Foo</code>.</li>
</ul>
<p>What we’ll be measuring is the cost of selectively applying a wrapper or interception and what that wrapper or interceptor adds in invocation time.</p>
<h3 id="frameworks">Frameworks</h3>
<p>Since we can’t test <em>all</em> of the <span class="smallcaps">DI</span> frameworks out there, I’ve made a—rather arbitrary—selection, based on, amongst others, what my colleagues and I use and want to use and frameworks I’m familiar with. These are the <span class="smallcaps">DI</span> frameworks we’ll look at:</p>
<ul>
<li><a href="https://autofac.org">Autofac</a></li>
<li><a href="http://www.ninject.org">Ninject</a></li>
<li><a href="https://simpleinjector.org">SimpleInjector</a></li>
<li><a href="https://github.com/unitycontainer/unity">Unity</a></li>
</ul>
<p>We’ll also (sometimes implicitly) look at a number of different interception frameworks:</p>
<ul>
<li><a href="http://www.castleproject.org/projects/dynamicproxy/">Castle DynamicProxy</a></li>
<li><a href="https://github.com/philiplaureano/LinFu">LinFu</a></li>
<li><a href="https://msdn.microsoft.com/en-us/library/dn178466(v=pandp.30).aspx">Unity Interception</a></li>
</ul>
<p>The benchmark code is <a href="https://github.com/heemskerkerik/InterceptionBenchmark">available on GitHub</a>, so you can plug in your framework of choice and see how it does.</p>
<h2 id="lets-see-the-code">Let’s see the code</h2>
<p>The code is pretty standard; a single method for each combination of frameworks we want to benchmark, decorated with a <code>[Benchmark]</code> attribute. Let’s see what it would normally look like.</p>
<pre><code class="language-csharp">[Benchmark]
void AutofacDecorator()
{
    var builder = new ContainerBuilder();

    builder.RegisterType&lt;Something&gt;()
           .Named&lt;ISomething&gt;(&quot;inner&quot;);
    builder.RegisterDecorator&lt;ISomething&gt;(
        (ctx, inner) =&gt; new TimingSomething(inner), 
        fromKey:&quot;inner&quot;);

    using (var container = builder.Build())
    {
        var something = container.Resolve&lt;ISomething&gt;();

        something.Foo();
    }
}
</code></pre>
<p>This is the entire pipeline, from registering the class and its decorator, to building the container and actually resolving and invoking.</p>
<p>The problem is that registering dependencies can be a pretty expensive operation, but because it’s something you almost always only do once at the start of your application, it’s not very interesting to benchmark.</p>
<h3 id="amortizing-initialization-cost">Amortizing initialization cost</h3>
<p>That’s a header I didn’t think I’d ever write in my free time. One approach to separating these costs is to use BenchmarkDotNet’s <a href="https://benchmarkdotnet.org/articles/features/setup-and-cleanup.html"><code>[Setup]</code> attribute</a>, which exists specifically for this purpose.</p>
<p>We’ll do container construction and registrations there, which mimics real use—because you wouldn’t build up a new container for each request, right? (<em>Right?!</em>)</p>
<pre><code class="language-csharp">IContainer _container;

[Setup]
void Initialize()
{
    var builder = new ContainerBuilder();

    builder.RegisterType&lt;Something&gt;()
           .Named&lt;ISomething&gt;(&quot;inner&quot;);
    builder.RegisterDecorator&lt;ISomething&gt;(
        (c, inner) =&gt; new TimingSomething(inner), 
        fromKey:&quot;inner&quot;);

    _container = builder.Build();
}

[Benchmark]
void AutofacDecorator()
{
    var something = container.Resolve&lt;ISomething&gt;();

    something.Foo();
}
</code></pre>
<p>The <code>Initialize</code> method gets called only once during test initialization, and the only thing that is actually benchmarked is actually resolving and invoking our <code>Something</code>.</p>
<h3 id="interception">Interception</h3>
<p>Our interceptor looks pretty much the same for every framework. It has some optimizations, working under the assumption that we can, one-time, gather a list of all the methods that need to be intercepted. For the sake of simplicity, that initialization has been dumbed down to specifically getting the <code>IFoo.Bar</code> method.</p>
<pre><code class="language-csharp">internal class DynamicProxyInterceptor : IInterceptor
{
    private static readonly HashSet&lt;MethodBase&gt; _matchingMethods;

    static DynamicProxyInterceptor()
    {
        _matchingMethods = 
            new HashSet&lt;MethodBase&gt;(new[] {GetMatchingMethod()});
    }

    public void Intercept(IInvocation invocation)
    {
        if(!ShouldInterceptMethod(
            invocation.MethodInvocationTarget))
        {
            invocation.Proceed();
            return;
        }

        var stopwatch = Stopwatch.StartNew();

        invocation.Proceed();

        stopwatch.Stop();
    }

    private static bool ShouldInterceptMethod(MethodBase method)
    {
        return _matchingMethods.Contains(method);
    }

    private static MethodBase GetMatchingMethod()
    {
        var type = typeof(Something);
        var interfaceMap = type.GetInterfaceMap(typeof(ISomething));

        int index = Array.FindIndex(
            interfaceMap.InterfaceMethods, 
            method =&gt; method.Name == 
                      nameof(ISomething.Foo));

        return interfaceMap.TargetMethods[index];
    }
}
</code></pre>
<h2 id="benchmark-results">Benchmark results</h2>
<p>I won’t go into the code in full detail, that’s for your own enjoyment. So without further ado, let’s see the results of our benchmark.</p>
<div class="nobreaktable">
<table><thead><tr>
<th>Method</th>
<th style="text-align:right">Median</th>
<th style="text-align:right"><abbr title="Standard deviation">StdDev</abbr></th>
<th style="text-align:right">Scaled</th>
<th style="text-align:right">Place</th>
</tr></thead>
<tbody>
<tr>
<td><code>NoDIDecorator</code></td>
<td style="text-align:right">56,0259 ns</td>
<td style="text-align:right"><nobr>1,0236 ns</nobr></td>
<td style="text-align:right">1,00</td>
<td style="text-align:right">1</td>
</tr>
<tr>
<td><code>SimpleInjectorDecorator</code></td>
<td style="text-align:right">122,5690 ns</td>
<td style="text-align:right">2,0028 ns</td>
<td style="text-align:right">2,19</td>
<td style="text-align:right">2</td>
</tr>
<tr>
<td><code>UnityDecorator</code></td>
<td style="text-align:right">1 113,9534 ns</td>
<td style="text-align:right">14,4703 ns</td>
<td style="text-align:right">19,88</td>
<td style="text-align:right">3</td>
</tr>
<tr>
<td><code>AutofacDecorator</code></td>
<td style="text-align:right">1 190,7673 ns</td>
<td style="text-align:right">37,3490 ns</td>
<td style="text-align:right">21,25</td>
<td style="text-align:right">4</td>
</tr>
<tr>
<td><code>NoDILinFu</code></td>
<td style="text-align:right">1 198,5308 ns</td>
<td style="text-align:right">25,1840 ns</td>
<td style="text-align:right">21,39</td>
<td style="text-align:right">4</td>
</tr>
<tr>
<td><code>NoDIDynamicProxy</code></td>
<td style="text-align:right">4 409,6544 ns</td>
<td style="text-align:right">53,3764 ns</td>
<td style="text-align:right">78,71</td>
<td style="text-align:right">5</td>
</tr>
<tr>
<td><code>SimpleInjectorDynamicProxy</code></td>
<td style="text-align:right">4 541,2929 ns</td>
<td style="text-align:right">84,6027 ns</td>
<td style="text-align:right">81,06</td>
<td style="text-align:right">5</td>
</tr>
<tr>
<td><code>NinjectDecorator</code></td>
<td style="text-align:right">14 394,0852 ns</td>
<td style="text-align:right">494,8733 ns</td>
<td style="text-align:right">256,92</td>
<td style="text-align:right">6</td>
</tr>
<tr>
<td><code>AutofacDynamicProxy</code></td>
<td style="text-align:right">15 736,7079 ns</td>
<td style="text-align:right">453,6272 ns</td>
<td style="text-align:right">280,88</td>
<td style="text-align:right">6</td>
</tr>
<tr>
<td><code>NinjectLinFu</code></td>
<td style="text-align:right">16 606,6503 ns</td>
<td style="text-align:right">455,9744 ns</td>
<td style="text-align:right">296,41</td>
<td style="text-align:right">6</td>
</tr>
<tr>
<td><code>NinjectDynamicProxy</code></td>
<td style="text-align:right">21 277,6210 ns</td>
<td style="text-align:right">568,0670 ns</td>
<td style="text-align:right">379,78</td>
<td style="text-align:right">7</td>
</tr>
<tr>
<td><code>UnityUnityInterception</code></td>
<td style="text-align:right">89 830,2951 ns</td>
<td style="text-align:right">1 618,7046 ns</td>
<td style="text-align:right">1 603,37</td>
<td style="text-align:right">8</td>
</tr>
</table>
</div>
<p>Some notes on these results:</p>
<ul>
<li>Methods starting with <code>NoDI</code> are not using any <span class="smallcaps">DI</span> framework, as a baseline.</li>
<li>If you want to see the specific versions of frameworks I’m using, see the project’s <a href="https://github.com/heemskerkerik/InterceptionBenchmark/blob/master/InterceptionBenchmarks/packages.config"><code>packages.config</code></a>.</li>
<li>Unity (natively) only supports its own interception framework, and that framework is—as far as I can tell—not usable outside of the context of Unity, so that’s why Unity is not combined with Castle DynamicProxy or LinFu and why none of the other frameworks are combined with Unity interception.</li>
<li>LinFu is only supported (for automatic interception) by Ninject; that’s why it made no sense to include Autofac + LinFu or SimpleInjector + LinFu, for example.</li>
</ul>
<h3 id="observations">Observations</h3>
<p>Unity interception and Ninject are both so slow compared to the competition, they immediately seem uninteresting. Ninject without any interception is barely faster than Autofac with DynamicProxy. As for Unity interception, it takes around <em>75 times longer</em> to intercept a method call using Unity interception than it does with LinFu.</p>
<p>SimpleInjector is on the other end of the spectrum; its scaled performance compared to the baseline (remember, that’s <em>not using any <span class="smallcaps">DI</span></em>) is still in the single digits, which is impressive!</p>
<p>Going back to LinFu, look at how fast it is! Kudos to its developers; LinFu stand-alone is even faster (admittedly, by a small margin) than Autofac without interception. It’s so fast that if you integrate it with a slow <span class="smallcaps">DI</span> framework, it’s almost like getting interception for free.</p>
<p>Other observations:</p>
<ul>
<li>Autofac and Unity are ‘meh’ in terms of performance; they don’t excel but are not excessively slow, either.</li>
<li>None of the <span class="smallcaps">DI</span> frameworks add a lot of overhead of their own when using an interception framework.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>First up the most obvious conclusion: <span class="smallcaps"><abbr title="Your Mileage May Vary">YMMV</abbr></span>. This is by no means a very realistic benchmark. You might encounter that certain frameworks perform a lot worse when they’re loaded down with thousands of registrations, or a lot better when you don’t use the most simple registration possible.</p>
<p>On the other hand, for most applications, time spent in resolving objects is not going to be the bottleneck, so don’t expect it to solve all of your problems. If you want to know if it will, use a profiler and <strong>measure first</strong>.</p>
<p>That being said, I still think it’s problematic if a <span class="smallcaps">DI</span> framework, which is by definition involved in a large part of your application, is ‘slow’. For that reason, I wouldn’t use Unity interception. I would also steer away from Ninject in new projects, unless there is <em>real</em> good reason to use it. Other than that, everything else we tested flies below the radar.</p>
<h2 id="ease-of-use">Ease of use</h2>
<p>I just want to touch on an aspect which is almost perpendicular to all we’ve been talking about, and that is ease of use. Some integrations between DI frameworks and interception frameworks are easy to use; they require little code and are easy to read and maintain. Others, not so much.</p>
<p>Let’s look at two examples that I find are at opposite ends of the spectrum. First up, <a href="https://github.com/heemskerkerik/InterceptionBenchmark/blob/master/InterceptionBenchmarks/UnityInterceptionBenchmark.cs">Unity</a>.</p>
<pre><code class="language-csharp">_container.AddNewExtension&lt;Interception&gt;();

_container.Configure&lt;Interception&gt;()
          .AddPolicy(&quot;Metering&quot;)
          .AddMatchingRule(new SingleMethodMatchingRule())
          .AddCallHandler(new UnityCallHandler());
</code></pre>
<p>This is the piece of code you configure once for your container, which says ‘I want to intercept some services with <code>Unity​Call​Handler</code>’.</p>
<pre><code class="language-csharp">_container.RegisterType&lt;ISomething, Something&gt;(
    new Interceptor&lt;InterfaceInterceptor&gt;(),
    new InterceptionBehavior&lt;PolicyInjectionBehavior&gt;()
);
</code></pre>
<p>This is a single registration. The <code>Interceptor</code> and <code>Interception​Behavior</code> will have to be repeated for <em>each</em> registration. That is a <em>lot</em> of code to simply say ‘I want to intercept calls to <code>ISomething</code>’.</p>
<p>Now let’s look at <a href="https://github.com/heemskerkerik/InterceptionBenchmark/blob/master/InterceptionBenchmarks/SimpleInjectorDynamicProxyBenchmark.cs">SimpleInjector</a>.</p>
<pre><code class="language-csharp">_container.InterceptWith&lt;DynamicProxyInterceptor&gt;(
    type =&gt; type == typeof(Something)
);
</code></pre>
<p>Again, the one-time setup. Note how much simpler it is.</p>
<pre><code class="language-csharp">_container.Register&lt;ISomething, Something&gt;(Lifestyle.Transient);
</code></pre>
<p>This is the registration of a service. Where’s the part about intercepting? It’s not there, which means you can truly ‘plug in’ interception using the single call to <code>Intercept​With</code>. This might be worth a lot more than performance (although SimpleInjector has little to complain about).</p>
<h2 id="outcome">Outcome</h2>
<p>I started this series of posts with the premise that I was <a href="https://www.erikheemskerk.nl/inspecting-aspects-interception/">tasked with adding instrumentation</a> to a project. Given that the project used Unity, my findings were another nail in Unity’s coffin. Long story short, we’re now looking at retro-fitting that project and are using SimpleInjector for future projects.</p>
<p>Oh, and if anybody is integrating LinFu with SimpleInjector, I’d love to know. Drop me a line!</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2016-09-19-Achtergrond-1.3.svg.png" medium="image" /></item><item><title>Inspecting aspects and interception in .NET, part 2</title><description>About how to benchmark code, which libraries to use and which pitfalls to avoid. How do we know what to measure and how?</description><link>https://www.erikheemskerk.nl/inspecting-aspects-interception-2/</link><guid isPermaLink="false">605618814fc9bc45be1c9a69</guid><category>Benchmarking</category><category>Dependency Injection</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Mon, 05 Sep 2016 03:32:00 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2016-09-05-Achtergrond-1.2.svg.png" alt="Inspecting aspects and interception in .NET, part 2" /><p><a href="https://www.erikheemskerk.nl/inspecting-aspects-interception/">Last time</a> we evaluated a number of ways to add instrumentation to your code while keeping your code maintainable and legible. We landed on using <a href="http://www.castleproject.org/projects/dynamicproxy/">interception</a> and the cliffhanger question from last time was: what’s the overhead? Is it fast enough? In this post, we will start looking at ways to answer this question.</p>
<h2 id="benchmarking-is-hard">Benchmarking is hard</h2>
<p>It’s not easy to <del>measure how fast your code runs</del> benchmark your code. There are misconceptions about how to measure how long something takes and what that means. You might think to capture the current date and time, run something in a loop, and again capture the current date and time. Take the difference, divide it by the number of iterations, and you have a relevant number. And you’d be wrong.</p>
<h3 id="dont-measure-using-datetime">Don’t measure using <code>DateTime</code></h3>
<p>Naively measuring your code’s run-time duration looks like this:</p>
<pre><code class="language-csharp">DateTime startTime = DateTime.Now;
LongRunningOperation();
DateTime endTime = DateTime.Now;
TimeSpan elapsed = endTime - startTime;
</code></pre>
<p>This is wrong for a number of reasons.</p>
<ul>
<li><code>DateTime</code> <a href="https://blogs.msdn.microsoft.com/ericlippert/2010/04/08/precision-and-accuracy-of-datetime/">does not have enough accuracy</a> to measure small increments;</li>
<li>Assuming you’re doing this in production code, you might have gone into or out of daylight savings time, giving you huge or negative readings;</li>
<li>Even if you’re using <code>Date​Time.Utc​Now</code>, the system time might have been adjusted—perhaps by an automatic time synchronization.</li>
</ul>
<p>There are probably more of these <a href="http://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time">falsehoods programmers believe about time</a> that apply to measuring how long your code took to execute.</p>
<p>They can all be easily fixed by using something designed for the task: <code>System.​Diagnostics.​Stopwatch</code>. It is a highly accurate, <a href="https://en.wikipedia.org/wiki/Monotonic_function#Monotonicity_in_calculus_and_analysis">monotonically increasing</a> counter which does not suffer from changes to date or time at all. But even <code>Stopwatch</code> does not have an infinite amount of accuracy.</p>
<h3 id="dont-run-your-code-just-once">Don’t run your code just once</h3>
<p>Let’s try to measure the run-time of a particularly small piece of code:</p>
<pre><code class="language-csharp">int x = 1;
var sw = Stopwatch.StartNew();
x++;
sw.Stop();
</code></pre>
<p>On my machine (<span class="smallcaps"><abbr title="Your Mileage May Vary">YMMV</abbr></span>), for about half the times, the elapsed time is zero, while the rest of the times, I get 300 nanoseconds. Neither of those sound plausible.</p>
<p>Let’s run it a whole lot of times and extrapolate a mean running time.</p>
<pre><code class="language-csharp">int x = 1;
var sw = Stopwatch.StartNew();

for (int i = 0; i &lt; 100000; i++)
{
	x++;
}

sw.Stop();
</code></pre>
<p>Now I’m getting a total run-time of around 67 000 nanoseconds. But there seems to be a lot of jitter in the results. Here are the results of ten consecutive runs.</p>
<blockquote>
<p>74 666,61 ns<br />
51 357,98 ns<br />
36 740,71 ns<br />
79 802,41 ns<br />
76 641,91 ns<br />
41 086,39 ns<br />
82 962,90 ns<br />
117 728,30 ns<br />
41 481,45 ns<br />
40 296,26 ns</p>
</blockquote>
<p>My number of iterations is probably still too small. I’d need to do some trial runs to figure out how many iterations I need to reduce the effects of jitter.</p>
<h3 id="unforeseen-consequences">Unforeseen consequences</h3>
<p>There are a lot of issues that crop up when you try to benchmark your code.</p>
<ul>
<li>The JIT compiler might add a delay to the first few calls to your code;</li>
<li>The garbage collector might randomly pause your code to clean up memory;</li>
<li>The benchmarking code itself also takes time to execute; you don’t want that included in the test results;</li>
<li>You might be interested in more statistics than the mean execution time (think median, percentiles, standard deviation...);</li>
<li>Compilers are pretty good at finding <a href="https://en.wikipedia.org/wiki/Dead_code">dead code</a>, so you need to make sure your code is not being optimized away;</li>
<li>Different runtimes might provide different results, so you probably want to see what running in x64 mode does, or when using (or not using) <a href="https://blogs.msdn.microsoft.com/dotnet/2013/09/30/ryujit-the-next-generation-jit-compiler-for-net/">RyuJIT</a>.</li>
</ul>
<p>There are a thousand other problems you might encounter if you try to write your own benchmarking framework. Benefit from the blood, sweat and tears of other people and use an existing, battle-hardened one. My library of choice is <a href="https://github.com/PerfDotNet/BenchmarkDotNet">BenchmarkDotNet</a>.</p>
<h3 id="benchmarkdotnet">BenchmarkDotNet</h3>
<p>Let’s examine what a benchmark looks like when using BenchmarkDotNet. For the sake of example, we’ll be benchmarking string concatenation. I want to compare the speed of <code>string.Concat</code>, <code>string.Format</code> and using a <code>String​Builder</code>. Yes, I am aware that the comparison is totally unfair. Just play along.</p>
<pre><code class="language-csharp">class StringConcatenation
{
    [Benchmark]
    public string UsingStringConcat()
    {
        return string.Concat(&quot;Hello,&quot;, &quot; &quot;, &quot;world!&quot;);
    }

    [Benchmark]
    public string UsingStringFormat()
    {
        return string.Format(&quot;{0}{1}{2}&quot;, &quot;Hello&quot;, &quot; &quot;, &quot;world!&quot;);
    }

    [Benchmark]
    public string UsingStringBuilder()
    {
        var builder = new StringBuilder();

        builder.Append(&quot;Hello,&quot;);
        builder.Append(&quot; &quot;);
        builder.Append(&quot;world!&quot;);

        return builder.ToString();
    }
}
</code></pre>
<p>Each method decorated with <code>[Benchmark]</code> is a candidate to be benchmarked, and when you start the process, like so:</p>
<pre><code class="language-csharp">BenchmarkRunner.Run&lt;StringConcatenation&gt;();
</code></pre>
<p>... BenchmarkDotNet will start running benchmarks. It will go through a number of <a href="https://github.com/PerfDotNet/BenchmarkDotNet#how-it-works">steps</a> to find the right iteration count, measure its own overhead and warm up the environment.</p>
<p>After a bit of thinking it will produce something like this:</p>
<pre><code class="language-plaintext">// * Summary *

Host Process Environment Information:
BenchmarkDotNet=v0.9.8.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700HQ CPU 2.60GHz, ProcessorCount=8
Frequency=2531249 ticks, Resolution=395.0619 ns, Timer=TSC
CLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE
GC=Concurrent Workstation
JitModules=clrjit-v4.6.1586.0

Type=StringConcatenation  Mode=Throughput  GarbageCollection=Concurrent Workstation

             Method |      Median |    StdDev |
------------------- |------------ |---------- |
  UsingStringConcat |  27.7441 ns | 0.5363 ns |
  UsingStringFormat | 125.0597 ns | 1.6980 ns |
 UsingStringBuilder |  53.8680 ns | 1.9694 ns |
</code></pre>
<p>Let’s look at the table at the bottom, which, incidentally, is valid <span class="smallcaps"><a href="https://guides.github.com/features/mastering-markdown/">GFM</a></span>.</p>
<div class="nobreaktable">
<table>
<caption>String concatenation benchmark</caption>
<colgroup><col /></colgroup>
<colgroup><col /><col /></colgroup>
<thead>
<tr><th>Method</th>
<th style="text-align:right">Median</th>
<th style="text-align:right">StdDev</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>UsingStringConcat</code></td>
<td style="text-align:right">27,7441 ns</td>
<td style="text-align:right">0,5363 ns</td>
</tr>
<tr>
<td><code>UsingStringFormat</code></td>
<td style="text-align:right">125,0597 ns</td>
<td style="text-align:right">1,6980 ns</td>
</tr>
<tr>
<td><code>UsingStringBuilder</code></td>
<td style="text-align:right">53,8680 ns</td>
<td style="text-align:right">1,9694 ns</td>
</tr>
</table>
</div>
<p>This is the final performance report. By default it lists the <a href="https://en.wikipedia.org/wiki/Median">median</a> duration and the <a href="https://en.wikipedia.org/wiki/Standard_deviation">standard deviation</a>. We can see from the median duration that in this run, on my machine, using the regular x86 JIT, <code>string.Concat</code> is almost twice as fast as <code>String​Builder</code>, and from the standard deviation that the duration is also much more stable.</p>
<p>I’ve heard and read rumors that <code>String​Builder</code> will be faster than <code>string.Concat</code> if you concatenate more than 5 substrings. Let’s find out.</p>
<pre><code class="language-csharp">class StringConcatenation
{
    [Benchmark]
    public string UsingStringConcat()
    {
        return string.Concat(&quot;Hello,&quot;, &quot; &quot;, &quot;world!&quot;, &quot; Something &quot;, &quot;else &quot;, &quot;entirely&quot;);
    }

    [Benchmark]
    public string UsingStringFormat()
    {
        return string.Format(&quot;{0}{1}{2}{3}{4}{5}&quot;, &quot;Hello&quot;, &quot; &quot;, &quot;world!&quot;, &quot; Something &quot;, &quot;else &quot;, &quot;entirely&quot;);
    }

    [Benchmark]
    public string UsingStringBuilder()
    {
        var builder = new StringBuilder();

        builder.Append(&quot;Hello,&quot;);
        builder.Append(&quot; &quot;);
        builder.Append(&quot;world!&quot;);
        builder.Append(&quot; Something &quot;);
        builder.Append(&quot;else &quot;);
        builder.Append(&quot;entirely&quot;);

        return builder.ToString();
    }
}
</code></pre>
<div class="nobreaktable">
<table>
<caption>Using 6 substrings</caption>
<colgroup><col /></colgroup>
<colgroup><col /><col /></colgroup>
<thead>
<tr><th>Method</th>
<th style="text-align:right">Median</th>
<th style="text-align:right">StdDev</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>UsingStringConcat</code></td>
<td style="text-align:right"><strong>125,4313 ns</strong></td>
<td style="text-align:right">2,3578 ns</td>
</tr>
<tr>
<td><code>UsingStringFormat</code></td>
<td style="text-align:right">250,0233 ns</td>
<td style="text-align:right">4,5334 ns</td>
</tr>
<tr>
<td><code>UsingStringBuilder</code></td>
<td style="text-align:right"><strong>164,9556 ns</strong></td>
<td style="text-align:right">2,2380 ns</td>
</tr>
</tbody>
</table>
</div>
<p><code>string.Format</code> is still trailing behind, but the gap between <code>string.Concat</code> and <code>String​Builder</code> has shrunk significantly. Let’s see what happens if we add more arguments.</p>
<div class="nobreaktable">
<table>
<caption>Using 9 substrings</caption>
<colgroup><col /></colgroup>
<colgroup><col /><col /></colgroup>
<thead>
<tr><th>Method</th>
<th style="text-align:right">Median</th>
<th style="text-align:right">StdDev</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>UsingStringConcat</code></td>
<td style="text-align:right"><strong>183,3770 ns</strong></td>
<td style="text-align:right">11,9913 ns</td>
</tr>
<tr>
<td><code>UsingStringFormat</code></td>
<td style="text-align:right">361,9582 ns</td>
<td style="text-align:right">5,2223 ns</td>
</tr>
<tr>
<td><code>UsingStringBuilder</code></td>
<td style="text-align:right"><strong>198,7081 ns</strong></td>
<td style="text-align:right">4,9657 ns</td>
</tr>
</tbody>
</table>
</div>
<p>Now things are getting interesting. The two methods are almost as fast, but it’s notable that <code>string.Concat</code>’s standard deviation is much higher than that of <code>String​Builder</code>.</p>
<div class="nobreaktable">
<table>
<caption>Using 14 substrings</caption>
<colgroup><col /></colgroup>
<colgroup><col /><col /></colgroup>
<thead>
<tr><th>Method</th>
<th style="text-align:right">Median</th>
<th style="text-align:right">StdDev</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>UsingStringConcat</code></td>
<td style="text-align:right"><strong>262,7727 ns</strong></td>
<td style="text-align:right">69,2226 ns</td>
</tr>
<tr>
<td><code>UsingStringFormat</code></td>
<td style="text-align:right">504,8921 ns</td>
<td style="text-align:right">15,0032 ns</td>
</tr>
<tr>
<td><code>UsingStringBuilder</code></td>
<td style="text-align:right"><strong>290,8394 ns</strong></td>
<td style="text-align:right">4,0046 ns</td>
</tr>
</tbody>
</table>
</div>
<p>The relative gap between the two now seems to be increasing, whereas the standard deviation is only increasing for <code>string.Concat</code>, and decreasing for <code>String​Builder</code>. Let’s go all out and double the number of arguments.</p>
<div class="nobreaktable">
<table>
<caption>Using 28 substrings</caption>
<colgroup><col /></colgroup>
<colgroup><col /><col /></colgroup>
<thead>
<tr><th>Method</th>
<th style="text-align:right">Median</th>
<th style="text-align:right">StdDev</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>UsingStringConcat</code></td>
<td style="text-align:right"><strong>500,1520 ns</strong></td>
<td style="text-align:right">31,3080 ns</td>
</tr>
<tr>
<td><code>UsingStringFormat</code></td>
<td style="text-align:right">882,1520 ns</td>
<td style="text-align:right">37,3244 ns</td>
</tr>
<tr>
<td><code>UsingStringBuilder</code></td>
<td style="text-align:right"><strong>475,3793 ns</strong></td>
<td style="text-align:right">14,2873 ns</td>
</tr>
</tbody>
</table>
</div>
<p>I ran this one a number of times to be sure, but it looks like <code>String​Builder</code> is finally faster than <code>string.Concat</code>! The conclusion? It takes around 28 substrings to be concatenated for <code>String​Builder</code> to be faster than <code>string.Concat</code> on my machine, in x86 mode, using .NET 4.6.2. I didn’t measure the exact tipping point, but I guesstimate it to be around 25 substrings. Interestingly enough, when using RyuJIT in x64 mode, <code>string.Concat</code> was <em>still</em> winning out.</p>
<h2 id="what-does-this-tell-us">What does this tell us?</h2>
<p>Very little. At most, it tells us that there is little to no reason to use <code>String​Builder</code> over <code>string.Concat</code> for scenarios where you know at compile-time which strings you will be concatenating, and you’re not using loops.</p>
<p>It’s important to realize that benchmarks can give you an indication of performance, but they are no replacement for a run-time metric or a profiler when it comes to finding hotspots. Also, <span class="smallcaps"><abbr title="Your Mileage May Vary">YMMV</abbr></span>; when you benchmark a piece of code using a certain runtime on a certain architecture, your results are <a href="https://github.com/PerfDotNet/BenchmarkDotNet#rules-of-benchmarking">only valid on that specific environment</a>. Nowhere else.</p>
<p>This, however, is what micro-benchmarks are about. They tell you if algorithm A is faster than algorithm B under specific circumstances, and by how much. When the difference is small, unless you have extremely stringent performance requirements, at most you might say: this won’t hurt us.</p>
<p>Now that we’ve looked at micro-benchmarking, <a href="https://www.erikheemskerk.nl/inspecting-aspects-interception-3/">next time</a> we’ll look at actually putting some <span class="smallcaps"><abbr title="Dependency Injection">DI</abbr></span> frameworks through their paces, combining it with interception, and trying to explain what we find.</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2016-09-05-Achtergrond-1.2.svg.png" medium="image" /></item><item><title>C# 7.0: Small but welcome improvements</title><description>Let’s take a look at C# 7.0’s features and what they have to offer.</description><link>https://www.erikheemskerk.nl/csharp-7-0-improvements/</link><guid isPermaLink="false">605618814fc9bc45be1c9a6b</guid><category>C#</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Mon, 29 Aug 2016 17:12:00 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2017-09-15-Achtergrond-2.1.svg.png" alt="C# 7.0: Small but welcome improvements" /><p>I know I <a href="https://www.erikheemskerk.nl/inspecting-aspects-interception/">said</a> the next post would be about benchmarking, and it was fully my intention, but then the real world happens. Recently, Microsoft announced the tentative <a href="https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/">feature-set of C# 7.0</a>. Glancing over it, it looks like a similar improvement to what <a href="https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6">C# 6.0</a> brought us; it brought us 12 relatively minor improvements, while C# 7.0 clocks in at around 10 improvements. Let’s look at the ones that stand out.</p>
<h2 id="out-variables">Out variables</h2>
<p>The gist of it is that instead of writing this:</p>
<pre><code class="language-csharp">string result;
if (dictionary.TryGetValue(key, out result))
    // it worked!
</code></pre>
<p>We can now write this:</p>
<pre><code class="language-csharp">if (dictionary.TryGetValue(key, out string result))
    // it worked!
</code></pre>
<p>It saves you an extra line of code and keeps you in the flow of typing, because you don’t have to jump back and add a variable. Time will tell if the time savings are worth the possible extra mental load.</p>
<h2 id="pattern-matching">Pattern matching</h2>
<p>C# is adopting more and more functional paradigms, but that shouldn’t surprise you; it’s a shift that started with the introduction of LINQ. Pattern matching is one of those, and while it’s currently limited in scope, only supporting <code>is</code> and <code>case</code>, there are plans to expand this.</p>
<p>Both <code>case</code> and <code>is</code> pattern matching will increase the number of cases where you can join a variable’s declaration and initialization. For me, that’s less mental load, so it increases readability. It also makes writing code where you’d normally use an <code>as</code> clause a lot more enjoyable to write.</p>
<p>In a supreme case of tunnel vision, I spent an inordinate amount of time wondering if <code>is</code> also supports the <code>when</code> clause, you know, for ‘additional conditions’. I thought to myself: I must download the <a href="https://blogs.msdn.microsoft.com/visualstudio/2016/08/22/visual-studio-15-preview-4/">Visual Studio “15” Preview</a> and find out.
<img src="/content/images/2016/08/PicardFacepalm.jpg" alt="I gave Jean-Luc Picard a headache" />
This is, of course, how you do ‘additional conditions’:</p>
<pre><code class="language-csharp">if ((shape is Rectangle s) &amp;&amp; s.Width == s.Length)
   // it's a square!
</code></pre>
<h2 id="tuples">Tuples</h2>
<p>Alright, let’s move past the stupidity and onto the next productivity enhancer. I think tuples are a great solution for when you have to return a very small number of values from a private method, like a query that returns values from a number of related columns. When you construct a tuple with more than four values, I think it’s high time to introduce a <span class="smallcaps"><abbr title="Data Transfer Object">DTO</abbr></span>, because readability will most likely suffer otherwise.</p>
<pre><code class="language-csharp">private (string honorific, string first, string middle, string last, string postNominal) ParseName(string name)
</code></pre>
<p>My eyes hurt from reading this. Please, think of my eyes and introduce a <span class="smallcaps">DTO</span> when you have too many values to return. What baffles me is that C# technically supports up to 8 (<em>eight!</em>) values in a single tuple. In my very humble opinion, if you use that many values, you need a stern talking to.</p>
<h2 id="deconstruction">Deconstruction</h2>
<p>Basically the consuming side’s counterpart of tuples, this feature’s main use case in my eyes is assigning values from a tuple to existing variables.</p>
<pre><code class="language-csharp">int x = 0, y = 0;
if (point != null)
    (x, y) = point;
</code></pre>
<p>Deconstructing tuples into new variables is only mildly useful, as you can just address the tuple’s members directly without having to create more variables (and possibly more mental load). Deconstructing arbitrary types is definitely only useful when using existing variables.</p>
<h2 id="local-functions">Local functions</h2>
<p>A colleague of mine asked “Can’t you achieve the same thing with a lambda expression?” You cannot. Lambda expressions don’t have a name, so they can’t really self-reference. You can write a solution for that with delegates, but it’s pretty ugly:</p>
<pre><code class="language-csharp">void WriteFibonacciSequence()
{
    Func&lt;int, int&gt; fib = null;

    fib = i =&gt;
    {
        if (i == 0)
            return 0;
        else if (i == 1)
            return 1;
        else
            return fib(i - 2) + fib(i - 1);
    };

    for (int i = 0; i &lt; 15; i++)
    {
        Console.WriteLine(fib(i));
    }
}
</code></pre>
<p>This is pretty fragile; if someone modifies the value of <code>fib</code> (by accident, of course), your code is broken. Local functions don’t have this problem.</p>
<pre><code class="language-csharp">void WriteFibonacciSequence()
{
    int Fib(int i)
    {
        if (i == 0)
            return 0;
        else if (i == 1)
            return 1;
        else
            return Fib(i - 2) + Fib(i - 1);
    };

    for (int i = 0; i &lt; 15; i++)
    {
        Console.WriteLine(Fib(i));
    }
}
</code></pre>
<p>Also, lambda expressions and anonymous delegates don’t support the <code>yield return</code> and <code>yield break</code> statements, while local functions do. Now you can finally stop writing separate private generator methods!</p>
<h2 id="literal-improvements">Literal improvements</h2>
<p>Small stuff, but the addition of binary literals is nice and being able to insert <code>_</code> as a separator at arbitrary positions might make some values more understandable.</p>
<h2 id="ref-returns-and-locals">Ref returns and locals</h2>
<pre><code class="language-csharp">void Foo()
{
   int[] values = { 1, 2, 3, 4, 5, 6 };
   ref int bar = ref GetBar(values);
   bar = 42; // values is now { 1, 2, 3, 4, 42, 6 }
}

ref int GetBar(int[] values)
{
   return values[4];
}
</code></pre>
<p>This is the counterpart to <code>ref</code> method arguments, and it completes the full implementation of ‘references to value types’, and it’s probably most useful to high performance, memory critical applications.</p>
<h2 id="generalized-async-return-types">Generalized async return types</h2>
<p>This is another counterpart to a feature introduced in an earlier language version. In this case it’s <code>async</code> and <code>await</code>. In C# 6.0 you could already <a href="https://blogs.msdn.microsoft.com/pfxteam/2011/01/13/await-anything/">await things that are not <code>Task</code></a>; now you can also return things that are not <code>Task</code> from an <code>async</code> method.</p>
<p>This is a feature aimed at library developers, because it’s basically only plumbing to make sure you are able to return <code>Foo</code> from an async method, provided that you <a href="https://github.com/dotnet/roslyn/issues/7169">implement</a> the async method builder and the awaitable.</p>
<h2 id="more-expression-bodied-members">More expression-bodied members</h2>
<p><a href="http://geekswithblogs.net/BlackRabbitCoder/archive/2015/05/14/c.net-little-wonders-expression-bodied-members-in-c-6.aspx">Expression-bodied members</a> are wonderfully useful. The community rallied and implemented them in some of the places were Microsoft didn’t, which is awesome.</p>
<h2 id="throw-expressions">Throw expressions</h2>
<p>Let’s look at one of the samples Microsoft provides:</p>
<pre><code class="language-csharp">public string Name { get; }
public Person(string name) =&gt; Name = name ?? throw new ArgumentNullException(nameof(name));
</code></pre>
<p>This is wonderfully specific and also doesn’t require any new methods or curly braces. I like it!</p>
<h2 id="conclusion">Conclusion</h2>
<p>C# 7.0 doesn’t make as big a splash as C# 5.0 did, but it contains some very useful features that are bound to enhance your development productivity. I’m looking forward to using this in production code.</p>
<p>C# was already a mixed-paradigm language since the introduction of LINQ, and I’m happy to see Microsoft is continuing down that path. One of the features that didn’t make it which I would have loved to see is <a href="https://roslyn.codeplex.com/discussions/541334">non-nullable reference types</a> but that is understandably a much harder problem to solve.</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2017-09-15-Achtergrond-2.1.svg.png" medium="image" /></item><item><title>Inspecting aspects and interception in .NET, part 1</title><description>I take a look at how to measure how often code executes and how long each execution takes and what the best ways are to do it.</description><link>https://www.erikheemskerk.nl/inspecting-aspects-interception/</link><guid isPermaLink="false">605618814fc9bc45be1c9a68</guid><category>Benchmarking</category><category>Dependency Injection</category><dc:creator>Erik Heemskerk</dc:creator><pubDate>Wed, 24 Aug 2016 03:47:00 GMT</pubDate><content:encoded><![CDATA[<img src="https://www.erikheemskerk.nl/content/images/2019/01/2016-08-24-Achtergrond-1.1.svg.png" alt="Inspecting aspects and interception in .NET, part 1" /><p>Recently I was tasked with adding instrumentation to a project: measuring how often methods were being called and how long each execution took. I had a couple of options.</p>
<ul>
<li>Timing it inside of the methods;</li>
<li>Use an adapter;</li>
<li>Use an aspect.</li>
</ul>
<h3 id="the-adapter-solution-and-why-not">The adapter solution and why not</h3>
<p>Inserting timing code directly into the methods would violate <a href="https://en.wikipedia.org/wiki/Separation_of_concerns">separation of concerns</a>, so it was never an option. The adapter solution consists of defining an adapter class which wraps calls with timing code. Let’s say we have a <code>Foo</code> class which implements <code>IFoo</code>:</p>
<pre><code class="language-csharp">interface IFoo
{
    void Bar();
}

class Foo: IFoo
{
    public void Bar()
    {
        // do important things here        
    }
}
</code></pre>
<p>Our adapter, <code>Foo​Timing​Adapter</code>, would then look something like this:</p>
<pre><code class="language-csharp">class FooTimingAdapter: IFoo
{
    private readonly IFoo _internalFoo;

    public FooTimingAdapter(IFoo internalFoo)
    {
        _internalFoo = internalFoo;
    }

    public void Bar()
    {
        var stopwatch = Stopwatch.StartNew();

        _internalFoo.Bar();

        Debug.WriteLine($&quot;IFoo.Bar took {stopwatch.Elapsed}&quot;);
    }
}
</code></pre>
<p>This is nice; it separates the concern of instrumentation from the actual implementation of the class. The problem, of course, is that when you have a lot of classes to instrument, the collection of adapter classes becomes a burden to maintain.</p>
<p>When you change <code>IFoo</code>, you not only have to change <code>Foo</code>, but also <code>Foo​Timing​Adapter</code>. Doing <span class="smallcaps"><a href="https://en.wikipedia.org/wiki/Test-driven_development">TDD</a></span>, as you should, this means you also have to write new, menial, tests for your adapter. This is starting to sound worse by the minute.</p>
<h3 id="aspects-to-the-rescue">Aspects to the rescue</h3>
<p>Instrumentation is a clear example of a cross-cutting concern: it is relevant to the program, but it is a different <em>concern</em> than, say, database access. It’s also generally highly reusable, which makes it a fine candidate for an <a href="https://en.wikipedia.org/wiki/Aspect-oriented_programming">aspect</a>.</p>
<p>The difficulty with aspects is how to integrate them into your code; calling them directly kind of defeats the point. You want to use metadata like a marker interface or an attribute to indicate where an aspect should be invoked. .NET leaves you with a few options.</p>
<ul>
<li>Instructing an <a href="https://en.wikipedia.org/wiki/Common_Intermediate_Language">Intermediate Language</a> (or <span class="smallcaps">IL</span>) weaver like <a href="https://github.com/Fody/Fody">Fody</a> where and how to insert calls to your aspect (which involves writing code that emits <span class="smallcaps">IL</span>);</li>
<li>Using an <span class="smallcaps"><abbr title="Aspect-Oriented Programming">AOP</abbr></span> framework like <a href="https://www.postsharp.net/">PostSharp</a>, which uses <span class="smallcaps">IL</span>-weaving under the hood but provides abstractions and interception points so you don’t have to write <span class="smallcaps">IL</span>;</li>
<li>Using a proxy generation framework like <a href="http://www.castleproject.org/projects/dynamicproxy/">Castle Dynamic​Proxy</a> to intercept calls.</li>
</ul>
<h3 id="interception">Interception</h3>
<p>Interception is all done at run-time. A proxy object is created which routes calls through an <em>interceptor</em>. Unlike the other methods, it’s only possible to intercept interfaces or class methods that are <code>abstract</code> or <code>virtual</code>. If you only want to intercept <em>some</em> calls, you’ll have to do filtering yourself, at run-time.</p>
<h4 id="alternatives-to-interception">Alternatives to Interception</h4>
<p>IL-emitting code quickly becomes <a href="https://github.com/Fody/PropertyChanged/blob/master/PropertyChanged.Fody/MethodInjector.cs#L62">complicated</a> and very hard to understand and maintain, and your aspects’ code becomes invisible when debugging your code. Performance is top notch, however, because the code executed is identical to just inserting the code yourself. You can apply aspects to all of your code, including <code>private</code> and <code>internal</code> methods. Only applying your aspect to <em>some</em> of your code is done compile-time, so it’s cost-free at run-time.</p>
<p>Tools like PostSharp tend to have a large impact on your build process and they often require (expensive) licenses for each developer that compiles your project. In a team of developers, that’s probably everybody. Performance is excellent and it is fairly easy to reason about what your code is doing when debugging. You can easily target individual methods, classes or <a href="http://doc.postsharp.net/attribute-multicasting#all-types">entire namespaces</a> and assemblies if you want, and this is again done compile-time.</p>
<h3 id="lets-do-interception">Let’s do interception</h3>
<p>Performance-wise and feature-wise, this is easily the worst option. Because it’s highly legible (relatively, of course) and has virtually no impact on your build process—very important when you’re doing <span class="smallcaps">TDD</span>—I still consider it to be the best option.</p>
<p>Let’s take a look at a very naive interceptor to time a single method, using Castle DynamicProxy.</p>
<pre><code class="language-csharp">class TimingInterceptor: IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        var methodToInstrument = 
            typeof(IFoo).GetMethod(nameof(IFoo.Bar));
        
        // is this the method we want to instrument?
        if (invocation.MethodInvocationTarget != methodToInstrument)
        {
            // no, so just call the original method
            invocation.Proceed();
            return;
        }

        // yes, time it!
        var stopwatch = Stopwatch.StartNew();

        // call the original method
        invocation.Proceed();

        Debug.WriteLine($&quot;IFoo.Bar took {stopwatch.Elapsed}&quot;);
    }
}
</code></pre>
<p>This is naive, for a couple of reasons. First of all, it only supports instrumenting a single method. We probably want an attribute to indicate which methods to instrument. Second of all, it does not handle failure of the intercepted method. It might not be important for this purpose, but exception handling is something you have to explicitly do in an interceptor.</p>
<p>Last of all, it uses reflection <em>each time</em> a method is called. A cache would reduce the impact of that, but there is an unavoidable cost from the interception framework which has to create an <code>IInvocation</code> for each call, and it might do some reflection to do that <em>each time</em> a method is called.</p>
<h3 id="benchmarking-time">Benchmarking time</h3>
<p>What’s the overhead of all that reflection and whatever else the interception framework is doing? We could reason about it, but measuring is better. Benchmarks often yield <a href="https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/">surprising results</a> (and it’s a lot of fun), so <a href="https://www.erikheemskerk.nl/inspecting-aspects-interception-2/">next time</a> we’ll dive into benchmarking interception, and looking at how <span class="smallcaps"><abbr title="Dependency Injection">DI</abbr></span> frameworks cope with this.</p>
]]></content:encoded><media:content url="https://www.erikheemskerk.nl/content/images/2019/01/2016-08-24-Achtergrond-1.1.svg.png" medium="image" /></item></channel></rss>