<?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">
  <channel>
    <title>Richard Clayton</title>
    <description>Stumbling my way through the great wastelands of enterprise software development.</description>
    <link>https://rclayton.silvrback.com/feed</link>
    <atom:link href="https://rclayton.silvrback.com/feed" rel="self" type="application/rss+xml"/>
    <category domain="rclayton.silvrback.com">Content Management/Blog</category>
    <language>en-us</language>
      <pubDate>Sat, 24 Apr 2021 19:14:00 -0700</pubDate>
    <managingEditor>rlc.blacksun@gmail.com (Richard Clayton)</managingEditor>
      <item>
        <guid>https://rclayton.silvrback.com/writing-better-apps-dependency-injection#51913</guid>
          <pubDate>Sat, 24 Apr 2021 19:14:00 -0700</pubDate>
        <link>https://rclayton.silvrback.com/writing-better-apps-dependency-injection</link>
        <title>Writing Better Apps: Dependency Injection</title>
        <description></description>
        <content:encoded><![CDATA[<p>Dependency Injection (DI) is a pattern where the &quot;things&quot; a component (function, class) depend on are passed into the component instead of the component trying to instantiate (or resolve it) itself.  This practice tends to make code a lot more robust by reducing a component&#39;s scope.  The DI pattern also simplifies the testing process by allowing developers to replace standard implementations of dependencies with mocks or test doubles.</p>

<p>For example, imagine a function that does not use dependency injection:</p>
<div class="highlight"><pre><span></span><span class="kr">import</span> <span class="nx">config</span> <span class="nx">from</span> <span class="s1">&#39;./config&#39;</span>
<span class="kr">import</span> <span class="nx">knex</span> <span class="nx">from</span> <span class="s1">&#39;knex&#39;</span>

<span class="nx">async</span> <span class="kd">function</span> <span class="nx">saveUserPreferences</span><span class="p">(</span><span class="nx">userPrefs</span>: <span class="kt">UserPreferences</span><span class="p">)</span><span class="o">:</span> <span class="nx">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="c1">// Ok, so how do I get the DB?  Maybe I should just instantiate it!</span>
    <span class="kr">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="nx">knex</span><span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">db</span><span class="p">)</span>

    <span class="nx">await</span> <span class="nx">db</span><span class="p">(</span><span class="s1">&#39;user_preferences&#39;</span><span class="p">)</span>
        <span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">userPrefs</span><span class="p">)</span>
        <span class="p">.</span><span class="nx">where</span><span class="p">({</span> <span class="nx">userId</span>: <span class="kt">userPrefs.userId</span> <span class="p">})</span>

    <span class="nx">db</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span>
<span class="p">}</span>
</pre></div>
<p>The obvious flaw in this example is that I have to instantiate the <code>db</code> in every function that wants to make the call.  Alternatively, if you&#39;re going to use a database connection pool, it becomes impossible since every repository function has its own instance.  Another issue is that we have to manage the database client&#39;s lifecycle (by calling <code>close</code> at the end).</p>

<p>Of course, we could have magically imported the database from another module:</p>
<div class="highlight"><pre><span></span><span class="kr">import</span> <span class="nx">db</span> <span class="nx">from</span> <span class="s1">&#39;./db&#39;</span>

<span class="kr">export</span> <span class="nx">async</span> <span class="kd">function</span> <span class="nx">saveUserPreferences</span><span class="p">(</span><span class="nx">userPrefs</span>: <span class="kt">UserPreferences</span><span class="p">)</span><span class="o">:</span> <span class="nx">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="nx">await</span> <span class="nx">db</span><span class="p">(</span><span class="s1">&#39;user_preferences&#39;</span><span class="p">)</span>
        <span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">userPrefs</span><span class="p">)</span>
        <span class="p">.</span><span class="nx">where</span><span class="p">({</span> <span class="nx">userId</span>: <span class="kt">userPrefs.userId</span> <span class="p">})</span>
<span class="p">}</span>
</pre></div>
<p>Now think about how we test this code.  What if I want to verify that the database is called with the correct signature?</p>
<div class="highlight"><pre><span></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">saveUserPreferences</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;./userPreferencesRepository&#39;</span>

<span class="nx">describe</span><span class="p">(</span><span class="s1">&#39;saveUserPreferences&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>

    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;should update preferences for the record with matching userId&#39;</span><span class="p">,</span> <span class="nx">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="c1">// hmmm... how do I do this?  Do I need to run the DB locally?</span>
      <span class="c1">// Do I execute the update and then call the database to see if the change was successful?</span>
    <span class="p">});</span>
<span class="p">})</span>
</pre></div>
<p>In the <a href="https://jestjs.io/">Jest</a> unit test framework, you can override a module using <code>jest.mock(&#39;./db&#39;)</code>.  You would then create a matching module in a relative folder called <code>__mocks__</code> (e.g., <code>__mocks__/db.ts</code>) with your test implementation.  However, this is not true for every language, and frankly, this pattern is ugly (sorry, Jest devs).</p>

<h3 id="inversion-of-control">Inversion of Control</h3>

<p>A much simpler alternative is to <em>invert the component responsible for controlling dependencies</em> to the caller (Inversion of Control).  Instead of <code>saveUserPreferences</code> resolving or instantiating <code>db</code> on its own, what if we passed the <code>db</code> to the function?</p>
<div class="highlight"><pre><span></span><span class="kr">import</span><span class="p">{</span> <span class="nx">Knex</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;knex&#39;</span>

<span class="kr">export</span> <span class="nx">async</span> <span class="kd">function</span> <span class="nx">saveUserPreferences</span><span class="p">(</span><span class="nx">db</span>: <span class="kt">Knex</span><span class="p">,</span> <span class="nx">userPrefs</span>: <span class="kt">UserPreferences</span><span class="p">)</span><span class="o">:</span> <span class="nx">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="nx">await</span> <span class="nx">db</span><span class="p">(</span><span class="s1">&#39;user_preferences&#39;</span><span class="p">)</span>
        <span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">userPrefs</span><span class="p">)</span>
        <span class="p">.</span><span class="nx">where</span><span class="p">({</span> <span class="nx">userId</span>: <span class="kt">userPrefs.userId</span> <span class="p">})</span>
<span class="p">}</span>
</pre></div>
<p>Unit testing becomes a lot easier:</p>
<div class="highlight"><pre><span></span><span class="kr">import</span><span class="p">{</span> <span class="nx">Knex</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;knex&#39;</span>
<span class="kr">import</span> <span class="p">{</span> <span class="nx">saveUserPreferences</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;./userPreferencesRepository&#39;</span>

<span class="nx">describe</span><span class="p">(</span><span class="s1">&#39;saveUserPreferences&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>

    <span class="kd">let</span> <span class="nx">db</span><span class="o">:</span> <span class="p">(</span><span class="nx">table</span>: <span class="kt">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">Knex</span>
    <span class="kd">let</span> <span class="nx">ctx</span>: <span class="kt">Knex</span>

    <span class="nx">beforeEach</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
       <span class="c1">// We are going to use a trick and partially implement the db.</span>
       <span class="c1">// Also, I probably have the type signatures of Knex incorrect.</span>
       <span class="kr">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="p">{</span>
            <span class="nx">update</span>: <span class="kt">jest.fn</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">ctx</span><span class="p">),</span>
            <span class="nx">where</span>: <span class="kt">jest.fn</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">()</span><span class="o">&gt;</span><span class="p">,</span>
       <span class="p">}</span> <span class="kr">as</span> <span class="nx">unknown</span> <span class="kr">as</span> <span class="nx">Knex</span>
        <span class="nx">db</span> <span class="o">=</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">ctx</span><span class="p">)</span>
    <span class="p">})</span>

    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;should update preferences for the record with matching userId&#39;</span><span class="p">,</span> <span class="nx">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="kr">const</span> <span class="nx">prefs</span> <span class="o">=</span> <span class="p">{</span>
        <span class="nx">userId</span>: <span class="kt">42</span><span class="p">,</span>
       <span class="nx">email</span><span class="o">:</span> <span class="s1">&#39;foo@bar.com&#39;</span><span class="p">,</span>
        <span class="nx">phone</span><span class="o">:</span> <span class="s1">&#39;555-867-5309&#39;</span><span class="p">,</span>
      <span class="p">}</span>
      <span class="nx">await</span> <span class="nx">saveUserPreferences</span><span class="p">(</span><span class="nx">db</span><span class="p">,</span> <span class="nx">prefs</span><span class="p">)</span>
      <span class="nx">expect</span><span class="p">(</span><span class="nx">db</span><span class="p">).</span><span class="nx">toHaveBeenCalledWith</span><span class="p">(</span><span class="s1">&#39;user_preferences&#39;</span><span class="p">)</span>
      <span class="nx">expect</span><span class="p">(</span><span class="nx">ctx</span><span class="p">.</span><span class="nx">update</span><span class="p">).</span><span class="nx">toHaveBeenCalledWith</span><span class="p">(</span><span class="nx">prefs</span><span class="p">)</span>
      <span class="nx">expect</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="nx">where</span><span class="p">).</span><span class="nx">toHaveBeenCalledWith</span><span class="p">({</span> <span class="nx">userId</span>: <span class="kt">prefs.userId</span> <span class="p">})</span>
    <span class="p">});</span>
<span class="p">})</span>
</pre></div>
<p>Of course, passing around <code>db</code> to every repository function will become a massive pain in the butt as the code base grows.  More importantly, it makes callers conscious that <code>saveUserPreferences</code> uses some RDBS they probably shouldn&#39;t know about.  In fact, our domain code should be relying on abstractions and not have any reference to <code>Knex</code> at all!  How can we use DI but hide implementation details from the caller?</p>

<h3 id="using-factories">Using Factories</h3>

<p>The answer is that you need a little bit of application code to hide the injection details.  A straightforward way of doing this is to wrap your <code>saveUserPreferences</code> in a factory function and take advantage of closure scope, allowing the function to reference <code>db</code> without the caller knowing:</p>
<div class="highlight"><pre><span></span><span class="kr">import</span><span class="p">{</span> <span class="nx">Knex</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;knex&#39;</span>

<span class="kr">export</span> <span class="kd">function</span> <span class="nx">saveUserPreferencesFactory</span><span class="p">(</span><span class="nx">db</span>: <span class="kt">Knex</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">async</span> <span class="p">(</span><span class="nx">userPrefs</span>: <span class="kt">UserPreferences</span><span class="p">)</span><span class="o">:</span> <span class="nx">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">await</span> <span class="nx">db</span><span class="p">(</span><span class="s1">&#39;user_preferences&#39;</span><span class="p">)</span>
            <span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">userPrefs</span><span class="p">)</span>
            <span class="p">.</span><span class="nx">where</span><span class="p">({</span> <span class="nx">userId</span>: <span class="kt">userPrefs.userId</span> <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Now, when your application starts, you can build an instance of the <code>saveUserPreferences</code> function and pass around the built instance, which has a reference to the <code>db</code> in the upper scope:</p>
<div class="highlight"><pre><span></span><span class="kr">const</span> <span class="nx">saveUserPreferences</span> <span class="o">=</span> <span class="nx">saveUserPreferencesFactory</span><span class="p">(</span><span class="nx">db</span><span class="p">);</span>

<span class="nx">await</span> <span class="nx">saveUserPreferences</span><span class="p">({</span>
  <span class="nx">userId</span>: <span class="kt">42</span><span class="p">,</span>
  <span class="nx">email</span><span class="o">:</span> <span class="s1">&#39;foo@bar.com&#39;</span><span class="p">,</span>
  <span class="nx">phone</span><span class="o">:</span> <span class="s1">&#39;555-867-5309&#39;</span><span class="p">,</span>
<span class="p">})</span>
</pre></div>
<h3 id="structuring-dependency-injected-applications">Structuring Dependency Injected Applications</h3>

<p>Now that you&#39;ve seen the general implementation of DI on a component level, how do you structure an application around this concept?</p>

<p>Generally, apps that use DI follow an inverted hierarchical instantiation model where components with the least dependencies are built first and injected into components that need only the newly built dependencies and on and on until you have a fully configured root component.  We commonly refer to this as an &quot;object graph.&quot;  This is arguably the most cumbersome part of DI.</p>

<p>An example of doing this manually looks like this:</p>
<div class="highlight"><pre><span></span><span class="c1">// lots of imports...</span>

<span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">getDeps</span><span class="p">(</span><span class="nx">config</span>: <span class="kt">Config</span><span class="p">)</span><span class="o">:</span> <span class="nx">Deps</span> <span class="p">{</span>

    <span class="kr">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="nx">knex</span><span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">db</span><span class="p">)</span>

    <span class="kr">const</span> <span class="nx">saveUserPreferences</span> <span class="o">=</span> <span class="nx">saveUserPreferencesFactory</span><span class="p">(</span><span class="nx">db</span><span class="p">)</span>
    <span class="kr">const</span> <span class="nx">getUserPreferences</span> <span class="o">=</span> <span class="nx">getUserPreferencesFactory</span><span class="p">(</span><span class="nx">db</span><span class="p">)</span>

    <span class="c1">// ...</span>

    <span class="kr">const</span> <span class="nx">userService</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">UserService</span><span class="p">(</span>
        <span class="nx">config</span><span class="p">.</span><span class="nx">max</span><span class="p">,</span>
        <span class="nx">saveUserPreferences</span><span class="p">,</span>
        <span class="nx">getUserPreferences</span>
    <span class="p">)</span>

   <span class="c1">// ...</span>

   <span class="kr">const</span> <span class="nx">createUserRoute</span> <span class="o">=</span> <span class="nx">createUserRoute</span><span class="p">(</span><span class="nx">userService</span><span class="p">);</span>

    <span class="c1">// ...</span>

   <span class="k">return</span> <span class="p">{</span>
     <span class="nx">createUserRoute</span><span class="p">,</span>
     <span class="c1">// ... Other top-level components used by a web server or something.</span>
   <span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>This approach to DI is valid but very manual (and frankly annoying).  The developer is responsible for resolving the correct order of dependency creation.  More importantly, all components are instantiated immediately.  Imagine the code base is an API server, but you wanted to write a couple of administrative command-line utilities using services/helper functions in the codebase.  You probably don&#39;t want to start database connections if you don&#39;t need to.  You certainly don&#39;t want an instance of the webserver starting.  So how do we accomplish this when the DI pattern has us creating our object graph on initialization?</p>

<p>The answer is that we want <a href="https://en.wikipedia.org/wiki/Lazy_loading">lazy loading</a>.  This means a component is only created when it is needed.  If the requested component depends on other components, those components are also constructed.  Using our previous example, if we wanted an instance of <code>UserService</code>, we would also need <code>saveUserPreferences</code> and <code>getUserPreferences</code>, both of which need <code>db</code>.</p>

<p>So how do we write the lazy loading code? </p>

<h3 id="dependency-injection-frameworks">Dependency Injection Frameworks</h3>

<p>The short answer is, we don&#39;t!</p>

<p>Lazy loading dependencies and resolving an object graph is a non-trivial programming task. It would be silly to try to write that functionality every time we want to build an application.  Instead, we can leverage various frameworks built by our community of developers that will do this for us.</p>

<p>In the JavaScript/TypeScript world, there are various Dependency Injection frameworks.  My personal favorite is a library called <a href="https://github.com/jeffijoe/awilix">Awilix</a>.</p>

<p>Let&#39;s rewrite our <code>getDeps</code> function using Awilix.</p>
<div class="highlight"><pre><span></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">NameAndRegistrationPair</span><span class="p">,</span> <span class="nx">asFunction</span><span class="p">,</span> <span class="nx">asClass</span><span class="p">,</span> <span class="nx">asValue</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;awilix&#39;</span>

<span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">getDeps</span><span class="p">(</span><span class="nx">config</span>: <span class="kt">Config</span><span class="p">)</span><span class="o">:</span> <span class="nx">NameAndRegistrationPair</span><span class="o">&lt;</span><span class="nx">Deps</span><span class="o">&gt;</span> <span class="p">{</span>

    <span class="k">return</span> <span class="p">{</span>
        <span class="c1">// &quot;asValue&quot; means &quot;this is a constant&quot;.</span>
        <span class="nx">config</span>: <span class="kt">asValue</span><span class="p">(</span><span class="nx">config</span><span class="p">),</span>

        <span class="c1">// &quot;cradle&quot; is a magic proxy that will auto resolve your dependency</span>
        <span class="c1">// by the &quot;field&quot; name in this NameAndRegistrationPair</span>
        <span class="nx">db</span>: <span class="kt">asFunction</span><span class="p">((</span><span class="nx">cradle</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">knex</span><span class="p">(</span><span class="nx">cradle</span><span class="p">.</span><span class="nx">config</span><span class="p">.</span><span class="nx">db</span><span class="p">)),</span>

        <span class="c1">// You can even deconstruct the cradle object.</span>
        <span class="nx">saveUserPreferences</span>: <span class="kt">asFunction</span><span class="p">(({</span> <span class="nx">db</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="nx">saveUserPreferencesFactory</span><span class="p">(</span><span class="nx">db</span><span class="p">)),</span>

        <span class="c1">// And if you use the pattern of having your factory function accept</span>
        <span class="c1">// an object of dependencies as it&#39;s first parameter, you can simply:</span>
        <span class="nx">getUserPreferences</span>: <span class="kt">asFunction</span><span class="p">(</span><span class="nx">getUserPreferencesFactory</span><span class="p">),</span>

        <span class="c1">// Injection into the constructor of an object.</span>
        <span class="nx">userService</span>: <span class="kt">asClass</span><span class="p">(</span><span class="nx">UserService</span><span class="p">),</span>

        <span class="c1">// There&#39;s actually a better way to use Awilix in libraries like</span>
        <span class="c1">// Express.  We will look at those in another post.</span>
        <span class="nx">createUserRoute</span>: <span class="kt">asFunction</span><span class="p">(</span><span class="nx">createUserRouteFactory</span><span class="p">),</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Now when you want to resolve a component, you pull it out of the Dependency Injection context:</p>
<div class="highlight"><pre><span></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">createContainer</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;awilix&#39;</span>
<span class="kr">import</span> <span class="nx">getDeps</span> <span class="nx">from</span> <span class="s1">&#39;./getDeps&#39;</span>

<span class="c1">// assuming you have constructed the config</span>
<span class="kr">const</span> <span class="nx">deps</span> <span class="o">=</span> <span class="nx">getDeps</span><span class="p">(</span><span class="nx">config</span><span class="p">)</span>

<span class="kr">const</span> <span class="nx">container</span> <span class="o">=</span> <span class="nx">createContainer</span><span class="p">().</span><span class="nx">register</span><span class="p">(</span><span class="nx">deps</span><span class="p">)</span>

<span class="c1">// To resolve a dependency, you can do it directly:</span>
<span class="kr">const</span> <span class="nx">userService</span> <span class="o">=</span> <span class="nx">container</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s1">&#39;userService&#39;</span><span class="p">)</span>

<span class="c1">// Alternatively, Awilix makes that cool dependency proxy available</span>
<span class="c1">// to callers:</span>
<span class="kr">const</span> <span class="p">{</span> <span class="nx">saveUserPreferences</span><span class="p">,</span> <span class="nx">getUserPreferences</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">container</span><span class="p">.</span><span class="nx">cradle</span>
</pre></div>
<h2 id="conclusion">Conclusion</h2>

<p>Using Dependency Injection will make your applications better.  The pattern applies to applications of all sizes.  Smaller applications probably don&#39;t need features like lazy-loading and dynamic dependency resolution and can avoid the complexity of adding a DI framework.  Larger applications tend to have large and complex dependency hierarchies. Leveraging a decent DI framework can simplify the structure and maintenance of the application.</p>

<p>In the next post, I will demonstrate some of the magical things you can do with a decent DI framework.  I hope this will encourage you to think about how you can write better apps.</p>

<h2 id="parting-thoughts">Parting Thoughts</h2>

<p>Despite DI as an essential pattern for code bases of any size, some developers gripe about the practice.  If you are new to programming and unsure, I am here to dispel any doubts.  I cannot think of a single, respectable software Thought Leader that thinks the DI &quot;pattern&quot; is bad.  When engineers complain about DI, they really mean the DI &quot;framework&quot; they are using.</p>

<p>Some people follow bad practices of directly instantiating components because they think they are practicing &quot;KISS&quot; (Keep It Simple Stupid).  These are probably the same people you inherited that shitty legacy codebase from that is impossible to maintain and as fragile as a snowflake in June.</p>
]]></content:encoded>
      </item>
      <item>
        <guid>https://rclayton.silvrback.com/winner-in-tic-tac-toe#51811</guid>
          <pubDate>Sat, 03 Apr 2021 22:28:00 -0700</pubDate>
        <link>https://rclayton.silvrback.com/winner-in-tic-tac-toe</link>
        <title>Algorithm: Winner in Tic-Tac-Toe</title>
        <description>In Rust!</description>
        <content:encoded><![CDATA[<p>There was a <a href="https://www.reddit.com/r/programming/comments/miuam0/a_google_interview_question_determine_if_someone/">thread on Reddit</a> that caught my interest the other day.  A Software Engineer at Google <a href="https://jrms-random-blog.blogspot.com/2021/03/a-google-interview-question.html">mentioned</a> his favorite interview question for potential candidates was to have them write an algorithm to determine if a tic-tac-toe game had a winner.  Thinking about the question inspired me to solve the algorithm.</p>

<p>To make the task more challenging, I decided to write the algorithm in Rust (a language I am learning but not competent in).</p>

<p>There are various strategies a developer might take in implementing an algorithm to determine the state of a tic-tac-toe board.  Most approaches revolve around matching winning combinations (to which there are only 8) to player positions (X&#39;s and O&#39;s).  The trick is determining the best way to evaluate the board state.</p>

<p>In this post, I&#39;m going to walk through my thought process and implementations for two similar strategies for implementing this algorithm.  You can skip all the prose and visit the actual code if you want: <a href="https://github.com/rclayton-the-terrible/solve-tic-tac-toe-rust">https://github.com/rclayton-the-terrible/solve-tic-tac-toe-rust</a></p>

<hr>

<h2 id="model-and-setup">Model and Setup</h2>

<p>Let&#39;s first start by establishing a simple domain model for a tic-tac-toe game:</p>
<div class="highlight"><pre><span></span><span class="c1">// Represents a value at a specific position on the</span>
<span class="c1">// game board.</span>
<span class="k">pub</span><span class="w"> </span><span class="k">enum</span> <span class="nc">PlaceValue</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="n">X</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="n">O</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="n">E</span><span class="w"> </span><span class="c1">// Empty</span>
<span class="p">}</span><span class="w"></span>

<span class="c1">// Represents the game board</span>
<span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">Board</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="n">row1</span>: <span class="p">[</span><span class="n">PlaceValue</span><span class="p">;</span><span class="mi">3</span><span class="p">],</span><span class="w"></span>
<span class="w">    </span><span class="n">row2</span>: <span class="p">[</span><span class="n">PlaceValue</span><span class="p">;</span><span class="mi">3</span><span class="p">],</span><span class="w"></span>
<span class="w">    </span><span class="n">row3</span>: <span class="p">[</span><span class="n">PlaceValue</span><span class="p">;</span><span class="mi">3</span><span class="p">],</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="k">impl</span><span class="w"> </span><span class="n">Board</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="c1">// A simple method for building</span>
<span class="w">    </span><span class="c1">// a game board from an array of place values.</span>
<span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">from</span><span class="p">(</span><span class="n">positions</span>: <span class="p">[</span><span class="n">PlaceValue</span><span class="p">;</span><span class="mi">9</span><span class="p">])</span><span class="w"> </span>-&gt; <span class="nc">Board</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="n">Board</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="n">row1</span>: <span class="p">[</span><span class="n">positions</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="w"> </span><span class="n">positions</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="w"> </span><span class="n">positions</span><span class="p">[</span><span class="mi">2</span><span class="p">]],</span><span class="w"></span>
<span class="w">            </span><span class="n">row2</span>: <span class="p">[</span><span class="n">positions</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span><span class="w"> </span><span class="n">positions</span><span class="p">[</span><span class="mi">4</span><span class="p">],</span><span class="w"> </span><span class="n">positions</span><span class="p">[</span><span class="mi">5</span><span class="p">]],</span><span class="w"></span>
<span class="w">            </span><span class="n">row3</span>: <span class="p">[</span><span class="n">positions</span><span class="p">[</span><span class="mi">6</span><span class="p">],</span><span class="w"> </span><span class="n">positions</span><span class="p">[</span><span class="mi">7</span><span class="p">],</span><span class="w"> </span><span class="n">positions</span><span class="p">[</span><span class="mi">8</span><span class="p">]],</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Our implementations (just functions) will have the following signature:</p>
<div class="highlight"><pre><span></span><span class="k">fn</span> <span class="p">(</span><span class="n">board</span>: <span class="kp">&amp;</span><span class="nc">Board</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Option</span><span class="o">&lt;</span><span class="n">PlaceValue</span><span class="o">&gt;</span><span class="w"></span>
</pre></div>
<p>I have a battery of unit tests and benchmarks that will evaluate the validity of each implementation, so if you want to try your own, you will register it in <code>src/tests.rs</code>:</p>
<div class="highlight"><pre><span></span><span class="cp">#[cfg(test)]</span><span class="w"></span>
<span class="k">mod</span> <span class="nn">tests</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">use</span><span class="w"> </span><span class="k">crate</span>::<span class="o">*</span><span class="p">;</span><span class="w"></span>
<span class="w">    </span><span class="k">use</span><span class="w"> </span><span class="k">crate</span>::<span class="n">PlaceValue</span>::<span class="o">*</span><span class="p">;</span><span class="w"></span>
<span class="w">    </span><span class="k">use</span><span class="w"> </span><span class="k">super</span>::<span class="n">test</span>::<span class="n">Bencher</span><span class="p">;</span><span class="w"></span>

<span class="w">    </span><span class="k">fn</span> <span class="nf">run_compliance_tests</span><span class="p">(</span><span class="n">eval_winner</span>: <span class="nc">fn</span><span class="w"> </span><span class="p">(</span><span class="n">board</span>: <span class="kp">&amp;</span><span class="nc">Board</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Option</span><span class="o">&lt;</span><span class="n">PlaceValue</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="c1">//... Test impl</span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="cp">#[test]</span><span class="w"></span>
<span class="w">    </span><span class="k">fn</span> <span class="nf">test_bit_wise_eval_winner</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="n">run_compliance_tests</span><span class="p">(</span><span class="n">bit_strategy</span>::<span class="n">eval_winner</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="cp">#[bench]</span><span class="w"></span>
<span class="w">    </span><span class="k">fn</span> <span class="nf">bench_bit_wise_eval_winner</span><span class="p">(</span><span class="n">b</span>: <span class="kp">&amp;</span><span class="nc">mut</span><span class="w"> </span><span class="n">Bencher</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="n">b</span><span class="p">.</span><span class="n">iter</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="n">run_compliance_tests</span><span class="p">(</span><span class="n">bit_strategy</span>::<span class="n">eval_winner</span><span class="p">));</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="cp">#[test]</span><span class="w"></span>
<span class="w">    </span><span class="k">fn</span> <span class="nf">test_loop_eval_winner</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="n">run_compliance_tests</span><span class="p">(</span><span class="n">loop_strategy</span>::<span class="n">eval_winner</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>

<span class="w">    </span><span class="cp">#[bench]</span><span class="w"></span>
<span class="w">    </span><span class="k">fn</span> <span class="nf">bench_loop_eval_winner</span><span class="p">(</span><span class="n">b</span>: <span class="kp">&amp;</span><span class="nc">mut</span><span class="w"> </span><span class="n">Bencher</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="n">b</span><span class="p">.</span><span class="n">iter</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="n">run_compliance_tests</span><span class="p">(</span><span class="n">loop_strategy</span>::<span class="n">eval_winner</span><span class="p">));</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<h2 id="loop-strategy">Loop Strategy</h2>

<p>My first thought was to take the most direct approach, a nested loop strategy, that iterated over winning combinations and checked if the gameboard had any of those combinations for either player.</p>

<p>In (my sort of) pseudo-code, it would look like this:</p>
<div class="highlight"><pre><span></span>for each winning combo [C]:
  for each value in the combo [c]:
    if current value at position is X:
      increment X matches
    if current value at position is O:
      increment O matches
  if X matches == 3
    return X as winner
  if O matches == 3
    return O as winner
default: return no winner
</pre></div>
<p>In the loop strategy, we define a winning combo as an array of &quot;1&quot;s and &quot;0&quot;s, where &quot;1&quot;s represent a required place value for that combo.  I defined all winning combos like this:</p>
<div class="highlight"><pre><span></span><span class="k">const</span><span class="w"> </span><span class="n">WINNING_POSITIONS</span>: <span class="p">[[</span><span class="kt">u8</span><span class="p">;</span><span class="mi">9</span><span class="p">];</span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w">    </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">],</span><span class="w"></span>
<span class="w">    </span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">],</span><span class="w"></span>
<span class="w">    </span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">],</span><span class="w"></span>
<span class="w">    </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">],</span><span class="w"></span>
<span class="w">    </span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">],</span><span class="w"></span>
<span class="w">    </span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">],</span><span class="w"></span>
<span class="w">    </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">],</span><span class="w"></span>
<span class="w">    </span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">]</span><span class="w"></span>
<span class="p">];</span><span class="w"></span>
</pre></div>
<p>The implementation matches pretty closely to the pseudo code:</p>
<div class="highlight"><pre><span></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">eval_winner</span><span class="p">(</span><span class="n">board</span>: <span class="kp">&amp;</span><span class="nc">Board</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Option</span><span class="o">&lt;</span><span class="n">PlaceValue</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">combo</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&amp;</span><span class="n">WINNING_POSITIONS</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">x_matches</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">o_matches</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="p">..</span><span class="n">combo</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="n">combo</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                </span><span class="k">continue</span><span class="p">;</span><span class="w"></span>
<span class="w">            </span><span class="p">}</span><span class="w"></span>
<span class="w">            </span><span class="c1">// value_on_board just gets the value at position</span>
<span class="w">            </span><span class="c1">// &quot;i&quot;, which is just the left-to-right, top-down</span>
<span class="w">            </span><span class="c1">// places on the 3x3 grid.</span>
<span class="w">            </span><span class="k">match</span><span class="w"> </span><span class="n">value_on_board</span><span class="p">(</span><span class="o">&amp;</span><span class="n">board</span><span class="p">,</span><span class="w"> </span><span class="n">i</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                </span><span class="n">X</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">x_matches</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w">                </span><span class="n">O</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">o_matches</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w">                </span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">()</span><span class="w"></span>
<span class="w">            </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">x_matches</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">X</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="n">o_matches</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">O</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="nb">None</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>The loop strategy is not bad.  It solves the problem directly and is not costly from a performance perspective, given that the algorithm will only experience 72 loop iterations (8 combos x 9 positions) at most.</p>

<p>However, with some additional complexity, we can optimize this algorithm a little bit.</p>

<h2 id="bitwise-strategy">Bitwise Strategy</h2>

<p>Instead of using arrays and nested loops, we could model both winning combos and player selections as bit arrays (actually, unsigned integers).</p>

<p>The winning combos are now modelled in binary:</p>
<div class="highlight"><pre><span></span><span class="k">const</span><span class="w"> </span><span class="n">WINNING_POSITIONS</span>: <span class="p">[</span><span class="kt">u16</span><span class="p">;</span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w">    </span><span class="mb">0b_111_000_000</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="mb">0b_000_111_000</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="mb">0b_000_000_111</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="mb">0b_100_100_100</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="mb">0b_010_010_010</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="mb">0b_001_001_001</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="mb">0b_100_010_001</span><span class="p">,</span><span class="w"></span>
<span class="w">    </span><span class="mb">0b_001_010_100</span><span class="w"></span>
<span class="p">];</span><span class="w"></span>
</pre></div>
<p>The algorithm changes slightly.  We don&#39;t need to loop through every position on the board multiple times to determine the winner:</p>
<div class="highlight"><pre><span></span>convert board positions for X&#39;s and O&#39;s into bit arrays
for each winning combo (as int):
   if (X &amp; combo) == combo:
     return X as winner
   if (O &amp; combo) == combo:
     return O as winner
default: return no winner
</pre></div>
<p>The key to this approach is the bitwise <code>&amp;</code> (AND) operator.</p>

<p>The bitwise AND operator is applied to two numbers (e.g. <code>402 &amp; 163</code>).  All <code>1</code> bits shared at the same position between both numbers are retained.  All other positions are filled with <code>0</code>s:</p>

<table><thead>
<tr>
<th>bits</th>
<th>8</th>
<th>7</th>
<th>6</th>
<th>5</th>
<th>4</th>
<th>3</th>
<th>2</th>
<th>1</th>
<th>0</th>
</tr>
</thead><tbody>
<tr>
<td>left (402)</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>right (163)</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td><strong>result</strong></td>
<td>0</td>
<td><strong>1</strong></td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td><strong>1</strong></td>
<td>0</td>
</tr>
</tbody></table>

<p>We already have all the winning combo positions defined as integers.  All we need to do is convert the board positions for X&#39;s and O&#39;s into integers as well:</p>
<div class="highlight"><pre><span></span><span class="k">struct</span> <span class="nc">XPosOPos</span><span class="p">(</span><span class="kt">u16</span><span class="p">,</span><span class="w"> </span><span class="kt">u16</span><span class="p">);</span><span class="w"></span>

<span class="k">fn</span> <span class="nf">board_to_bits</span><span class="p">(</span><span class="n">board</span>: <span class="kp">&amp;</span><span class="nc">Board</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">XPosOPos</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">x_bits</span>: <span class="kt">u16</span> <span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">o_bits</span>: <span class="kt">u16</span> <span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="p">..</span><span class="mi">9</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">row</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_row</span><span class="p">(</span><span class="o">&amp;</span><span class="n">board</span><span class="p">,</span><span class="w"> </span><span class="n">i</span><span class="p">);</span><span class="w"></span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">position</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_row_pos</span><span class="p">(</span><span class="n">i</span><span class="p">);</span><span class="w"></span>
<span class="w">        </span><span class="k">match</span><span class="w"> </span><span class="n">row</span><span class="p">[</span><span class="n">position</span><span class="p">]</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="n">PlaceValue</span>::<span class="n">X</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">x_bits</span><span class="w"> </span><span class="o">|=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o">&lt;&lt;</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"></span>
<span class="w">            </span><span class="n">PlaceValue</span>::<span class="n">O</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">o_bits</span><span class="w"> </span><span class="o">|=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o">&lt;&lt;</span><span class="w"> </span><span class="n">i</span><span class="p">,</span><span class="w"></span>
<span class="w">            </span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">(),</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="n">XPosOPos</span><span class="p">(</span><span class="n">x_bits</span><span class="p">,</span><span class="w"> </span><span class="n">o_bits</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>The functions <code>get_row</code> and <code>get_row_pos</code> are just helpers that translate <code>i</code> (the index in our 9-bit array) to the 3x3 grid of tic-tac-toe.  With that position, we determine whether the value is &quot;X&quot;, &quot;O&quot;, or &quot;E&quot; (empty).  If it&#39;s X or O, we need to set the bit at position <code>i</code> in that integer to <code>1</code>.  We can set the bit using the bitwise <code>|</code> (OR) and the left shift <code>&lt;&lt;</code> operators.</p>

<p>The bitwise OR operator is applied to two numbers (e.g. <code>402 &amp; 163</code>).  For all bit positions, if a <code>1</code> occurs in either number, the value of that bit position becomes <code>1</code>.  All positions where <strong>both</strong> numbers have a <code>0</code> remain <code>0</code>:</p>

<table><thead>
<tr>
<th>bits</th>
<th>8</th>
<th>7</th>
<th>6</th>
<th>5</th>
<th>4</th>
<th>3</th>
<th>2</th>
<th>1</th>
<th>0</th>
</tr>
</thead><tbody>
<tr>
<td>left (402)</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>right (163)</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td><strong>result</strong></td>
<td><strong>1</strong></td>
<td><strong>1</strong></td>
<td>0</td>
<td><strong>1</strong></td>
<td><strong>1</strong></td>
<td>0</td>
<td>0</td>
<td><strong>1</strong></td>
<td><strong>1</strong></td>
</tr>
</tbody></table>

<p>The left shift operator <code>&lt;&lt;</code> is an operator that shifts all bits on the operator&#39;s left-hand side by <code>N</code> positions (specified by the value on the right-side). For example, <code>0b001 &lt;&lt; 1</code> results in <code>0b010</code> or <code>2</code>.</p>

<p>When the bitwise OR (<code>|</code>) and left-shift (<code>&lt;&lt;</code>) operators are combined, you can set the bit at a specific position in a number.  To do this, we make a &quot;mask&quot; -- a bit array with the <code>1</code> in the correct position.  We then perform a bitwise OR between the number whose bit we want to set and the mask.  For example, say we want to set the 6th bit to <code>1</code>:</p>
<div class="highlight"><pre><span></span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">22</span><span class="p">;</span><span class="w"></span>
<span class="c1">// move the 1 bit to the 6th position; 0b000_000_001 becomes 0b000_100_000</span>
<span class="kd">let</span><span class="w"> </span><span class="n">mask</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o">&lt;&lt;</span><span class="w"> </span><span class="mi">6</span><span class="p">;</span><span class="w"></span>
<span class="n">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">mask</span><span class="p">;</span><span class="w"></span>
<span class="c1">// Alternatively, Rust has |= operator similar to +=</span>
<span class="n">n</span><span class="w"> </span><span class="o">|=</span><span class="w"> </span><span class="n">mask</span><span class="p">;</span><span class="w"></span>
</pre></div>
<p>Now that we understand how the board state is translated into two integers (bit arrays) representing the X and O positions, let&#39;s look at the algorithm for determining the winner:</p>
<div class="highlight"><pre><span></span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">eval_winner</span><span class="p">(</span><span class="n">board</span>: <span class="kp">&amp;</span><span class="nc">Board</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Option</span><span class="o">&lt;</span><span class="n">PlaceValue</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">XPosOPos</span><span class="p">(</span><span class="n">x_bits</span><span class="p">,</span><span class="w"> </span><span class="n">o_bits</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">board_to_bits</span><span class="p">(</span><span class="o">&amp;</span><span class="n">board</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">pos</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&amp;</span><span class="n">WINNING_POSITIONS</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pos</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">x_bits</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="o">*</span><span class="n">pos</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">PlaceValue</span>::<span class="n">X</span><span class="p">);</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">pos</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">o_bits</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="o">*</span><span class="n">pos</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="n">PlaceValue</span>::<span class="n">O</span><span class="p">);</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="nb">None</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>All we do now is iterate over combo positions and check to see if the combo matches the X and O positions (<code>(pos &amp; x_bits) == *pos</code>).</p>

<p>It&#39;s easier to see what&#39;s happening with this expression by looking at an example:</p>
<div class="highlight"><pre><span></span><span class="p">(</span><span class="mb">0b010</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="mb">0b111</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mb">0b010</span><span class="w"></span>
<span class="c1">// simplifies to:</span>
<span class="mb">0b010</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mb">0b010</span><span class="w"></span>
</pre></div>
<h2 id="comparison-of-strategies">Comparison of Strategies</h2>

<p>The loop and bitwise strategies are both effective.  As I mentioned before, the worst-case scenario for the loop strategy is 72 loop iterations.  The bitwise method accomplishes the same result with a maximum of 17 loop iterations (9 to create the X and O bit positions and 8 to loop through the combos).  Of course, the body of the loops are also different - so perhaps not an apt comparison.</p>

<p>In terms of memory, the bitwise approach uses fewer resources.  The loop approach has to store 72 integers (8, 9 element arrays) plus two integers for the intermediate state during iterations.  The bitwise strategy uses eight integers for combo state and two integers for the X and O board state.</p>

<p><em>Both approaches use a minimal amount of memory and would be acceptable in a production system.  The loop method is arguably easier to maintain.  However, if you are interviewing or completing a Leetcode challenge, you will want to optimize for the fewest resources.</em></p>

<p>So what do the benchmarks look like?:</p>
<div class="highlight"><pre><span></span><span class="o">&gt;</span> <span class="nt">cargo</span> <span class="nt">bench</span>
    <span class="nt">Finished</span> <span class="nt">bench</span> <span class="cp">[</span><span class="nx">optimized</span><span class="cp">]</span> <span class="nt">target</span><span class="o">(</span><span class="nt">s</span><span class="o">)</span> <span class="nt">in</span> <span class="nt">0</span><span class="p">.</span><span class="nc">00s</span>
     <span class="nt">Running</span> <span class="nt">unittests</span> <span class="o">(</span><span class="nt">target</span><span class="o">/</span><span class="nt">release</span><span class="o">/</span><span class="nt">deps</span><span class="o">/</span><span class="nt">solve_tic_tac_toe-2ba3cdb4b32fcc5e</span><span class="o">)</span>

<span class="nt">running</span> <span class="nt">5</span> <span class="nt">tests</span>
<span class="nt">test</span> <span class="nt">bit_strategy</span><span class="p">::</span><span class="nd">tests</span><span class="p">::</span><span class="nd">test_board_to_bits</span> <span class="o">...</span> <span class="nt">ignored</span>
<span class="nt">test</span> <span class="nt">tests</span><span class="p">::</span><span class="nd">tests</span><span class="p">::</span><span class="nd">test_bit_wise_eval_winner</span> <span class="o">...</span> <span class="nt">ignored</span>
<span class="nt">test</span> <span class="nt">tests</span><span class="p">::</span><span class="nd">tests</span><span class="p">::</span><span class="nd">test_loop_eval_winner</span> <span class="o">...</span> <span class="nt">ignored</span>
<span class="nt">test</span> <span class="nt">tests</span><span class="p">::</span><span class="nd">tests</span><span class="p">::</span><span class="nd">bench_bit_wise_eval_winner</span> <span class="o">...</span> <span class="nt">bench</span><span class="o">:</span>          <span class="nt">97</span> <span class="nt">ns</span><span class="o">/</span><span class="nt">iter</span> <span class="o">(+/</span><span class="nt">-</span> <span class="nt">4</span><span class="o">)</span>
<span class="nt">test</span> <span class="nt">tests</span><span class="p">::</span><span class="nd">tests</span><span class="p">::</span><span class="nd">bench_loop_eval_winner</span>     <span class="o">...</span> <span class="nt">bench</span><span class="o">:</span>         <span class="nt">581</span> <span class="nt">ns</span><span class="o">/</span><span class="nt">iter</span> <span class="o">(+/</span><span class="nt">-</span> <span class="nt">44</span><span class="o">)</span>

<span class="nt">test</span> <span class="nt">result</span><span class="o">:</span> <span class="nt">ok</span><span class="o">.</span> <span class="nt">0</span> <span class="nt">passed</span><span class="o">;</span> <span class="nt">0</span> <span class="nt">failed</span><span class="o">;</span> <span class="nt">3</span> <span class="nt">ignored</span><span class="o">;</span> <span class="nt">2</span> <span class="nt">measured</span><span class="o">;</span> <span class="nt">0</span> <span class="nt">filtered</span> <span class="nt">out</span><span class="o">;</span> <span class="nt">finished</span> <span class="nt">in</span> <span class="nt">10</span><span class="p">.</span><span class="nc">38s</span>
</pre></div>
<p><strong>The bitwise approach is nearly five times faster!</strong></p>

<h2 id="conclusion">Conclusion</h2>

<p>I thought this was an exciting project to apply Rust and optimization techniques like bitwise operators. Tic-tac-toe is not the most practical thing to optimize for, but the solutions demonstrate bitwise operators&#39; potential if you need to eke out extra performance from some code.</p>

<p>If you thought the bitwise operators were neat, please check out this course on Educative: <a href="https://www.educative.io/courses/bit-manipulation">Master Solving Problems using Bit Manipulation</a>.</p>
]]></content:encoded>
      </item>
      <item>
        <guid>https://rclayton.silvrback.com/unified-observability#51735</guid>
          <pubDate>Sat, 20 Mar 2021 22:08:48 -0700</pubDate>
        <link>https://rclayton.silvrback.com/unified-observability</link>
        <title>Unified Observability</title>
        <description>Thinking about the future of logging, metrics, and tracing in applications.</description>
        <content:encoded><![CDATA[<p>The last decade has seen exciting developments in runtime observability of services.  Log aggregation and search are a requirement for operationalizing systems.   Organizations are collecting real-time metrics from production systems and using that data to get insight into customer behavior.  Most importantly, distributed tracing is becoming mainstream.</p>

<p>Despite the increasing awareness of observability practices, there remain many barriers to adopting these technologies.  Perhaps the most significant obstacle is integrating libraries/frameworks into services.  For instance, it&#39;s unlikely every observability tool will have a client for your platform. </p>

<p><img alt="Unsupported Jaeger Clients" src="https://rclayton-silvrback-com.s3.amazonaws.com/images/unified-observability/jaeger-missing-clients.png" /></p>

<p>Relying directly on clients can also be a problem.  Observability tools differentiate themselves from competitors by offering unique features.  If you use those features, it will be hard to move off the client if your new provider does not have a similar feature set.  Instead, your service could rely on a standardized abstraction.  For example, <a href="https://opentelemetry.io/">OpenTelemetry</a> offers client abstractions for Tracing and Metrics collection.  Some languages, like Java, have similar abstractions for logging (<a href="http://www.slf4j.org/">SLF4J</a>).</p>

<p>The biggest issue with client integrations is how intrusive the implementation can be in your domain code.  Look at the example from the OpenTracing website:</p>
<div class="highlight"><pre><span></span><span class="c1">// https://opentelemetry.io/docs/js/instrumentation/</span>

<span class="kd">function</span> <span class="nx">doWork</span><span class="p">(</span><span class="nx">parent</span><span class="p">)</span> <span class="p">{</span>
  <span class="kr">const</span> <span class="nx">span</span> <span class="o">=</span> <span class="nx">tracer</span><span class="p">.</span><span class="nx">startSpan</span><span class="p">(</span><span class="s1">&#39;doWork&#39;</span><span class="p">,</span> <span class="p">{</span>
    <span class="nx">parent</span><span class="p">,</span> <span class="nx">attributes</span><span class="o">:</span> <span class="p">{</span> <span class="nx">attribute1</span> <span class="o">:</span> <span class="s1">&#39;value1&#39;</span> <span class="p">}</span>
  <span class="p">});</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">40000000</span><span class="p">);</span> <span class="nx">i</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// empty</span>
  <span class="p">}</span>
  <span class="nx">span</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">&#39;attribute2&#39;</span><span class="p">,</span> <span class="s1">&#39;value2&#39;</span><span class="p">);</span>
  <span class="nx">span</span><span class="p">.</span><span class="nx">end</span><span class="p">();</span>
<span class="p">}</span>
</pre></div>
<p>Of course, there are ways to abstract some of these &quot;crosscutting concerns,&quot; but that assumes the framework in which you write code is flexible enough to be modified to perform some of this work.  For instance, we implemented an &quot;auto span&quot; plugin for Knex.js to transparently record database calls in OpenTracing (Jaeger backend).  However, implementing the plugin required a service library with an explicit extension point (something you will not get out of the box with many HTTP frameworks).</p>

<p>So far, I have spoken mainly about the &quot;client-side&quot; integration pattern for observability tools, which looks something like this:</p>

<p><img alt="Client-side Integration" src="https://rclayton-silvrback-com.s3.amazonaws.com/images/unified-observability/client-side-integration.png" /></p>

<p>Most architectures don&#39;t use pure client integration anymore.  Aggregating data from the client directly to the backing store doesn&#39;t make sense with thousands of processes in your infrastructure.  Client-side aggregation suffers typical operational challenges:</p>

<ul>
<li> Configuration management (including refreshing for running services)</li>
<li> Service discovery (where is the observability endpoint?)</li>
<li> Reliability (throttling, circuit breaking, retries, etc.)</li>
<li> Security (should the service be communicating to the endpoint?)</li>
</ul>

<p>Client-side integration also has the downside of placing a lot of the logic (and therefore processing burden) on the local process.  So not only is the service coordinating actions for its clients, but it&#39;s also managing connections to Statsd, serving Prometheus metrics requests, formatting logs, etc.</p>

<h2 id="standardizing-on-daemons-sidecars">Standardizing on Daemons/Sidecars</h2>

<p>The general solution to client-side integration problems is to move the processing to a more centralized service.  The industry has developed two standard deployment patterns to support this use case: daemons and sidecars:</p>

<p><img alt="Partial Sidecar/Daemon Integration" src="https://rclayton-silvrback-com.s3.amazonaws.com/images/unified-observability/partial-sidecar-daemon-integration.png" /></p>

<p>A daemon is an external service that provides functionality to a traditional business application.  In this case, the daemon (Fluentd) parsing and aggregating logs.  Daemons deploy as single-instance services on a host (bare-metal or VM).  Fluentd, a log aggregator, will consume all the <em>stdout</em> and <em>stderr</em> streams in Docker and forward the entries to one or more external endpoints.</p>

<p>Sidecars deploy with an application (think &quot;sidecar&quot; on a motorcycle) and only serve that peer application.  A typical use case for sidecars is to provide secrets and configurations specific to its peer service.</p>

<p>The daemon and sidecar patterns are more effective when providing multiple services (e.g., logging, metrics, tracing).  However, until recently, few open-source frameworks offered multiple observability modes.  The first multi-service daemons were commercial offerings like Datadog:</p>

<p><img alt="Multi-service Daemons/Sidecars" src="https://rclayton-silvrback-com.s3.amazonaws.com/images/unified-observability/commerical-sidecar-daemon-integration.png" /></p>

<p>Datadog&#39;s integration, however, is far from perfect.  Services are still required to use multiple strategies/protocols to aggregate data to the daemon.  For instance, Datadog will collect logs from stdout but still requires services to use the Statsd protocol for metrics and the OpenTelemetry library for traces.  What makes the integration more painful is that none of these tools integrate out-of-the-box.  Developers have to propagate IDs between logs and traces to correlate them in the dashboard.</p>

<p>I generally think the daemon pattern (and not the sidecar) is the best pattern for providing observability into services.  I also think having the daemon own the entire process is the ideal approach for integrating services.  The only thing missing is a unified mechanism for delivering logs, metrics, and traces to the daemon.</p>

<p><img alt="Unified Integration" src="https://rclayton-silvrback-com.s3.amazonaws.com/images/unified-observability/unified-integration.png" /></p>

<p>The key to the unified integration pattern is that the service, for the most part, is unaware of it.  Instead, services publish a standardized event to an &quot;observability stream.&quot;  The event can be a log, trace, or metric.  More likely, the event is all three; it blurs the distinction which better models reality and not the separation of tools.</p>

<p>Aggregation is a result of a daemon monitoring the observability stream.  The daemon would interpret events, transforming them if needed, and forward them to the correct backing store.  More importantly, if backing stores can perform cross-tool correlation, the daemon has the context necessary to provide the correlating identifiers.</p>

<p><img alt="Event Decomposition" src="https://rclayton-silvrback-com.s3.amazonaws.com/images/unified-observability/event-decomposition.png" /></p>

<p>If the standard way of producing observability events is stdout (the console), virtually any system could integrate with the aggregation system.  Using stdout could be a problem in systems with large capacity hosts and a high density of services.  An alternative approach might be using Kafka as the event stream.</p>

<p>Either way, a stream of observability events has the advantage of allowing multiple providers to listen to a stream.  I would discourage having a daemon for each observability mode (logging, metrics, tracing).  However, using different daemons as means of testing or transitioning between providers would be a powerful paradigm.  Imagine being able to launch an AWS daemon alongside your Datadog agent so you could explore the newest features of CloudWatch or XRay.  If you were using a persistent stream, you could even migrate old data into the new system with little effort.</p>

<h2 id="standard-observability-event">Standard Observability Event</h2>

<p>Our ability to get to a unified observability stream would require the industry to standardize on an event schema.  I think now, more than ever, we may have the impetus in the industry to reach this goal.  OpenTelemetry and <a href="https://cloudevents.io/">CloudEvents</a> are great examples of organizations coming together under a standard.   CloudEvents would probably be the envelope schema used by a Unified Observability Event.</p>

<p>So what would the developer experience be like with unified observability events? </p>

<p>The best solution would look like logging:</p>
<div class="highlight"><pre><span></span><span class="nx">log</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s1">&#39;Request received to update user profile.&#39;</span><span class="p">)</span>
</pre></div>
<p>Admittedly, this is not a very impressive example.  However, when combined with context from the request:</p>
<div class="highlight"><pre><span></span><span class="kr">export</span> <span class="k">default</span> <span class="p">[</span>
  <span class="c1">// Of course, this should be registered at the app level,</span>
  <span class="c1">// unless there are request-specific metadata to add.</span>
  <span class="nx">observe</span><span class="p">.</span><span class="nx">startContext</span><span class="p">({</span>
    <span class="nx">component</span><span class="o">:</span> <span class="s1">&#39;UpdateUserProfile&#39;</span><span class="p">,</span>
    <span class="nx">tags</span><span class="o">:</span> <span class="p">{</span>
      <span class="nx">add</span><span class="o">:</span> <span class="p">{</span>
        <span class="nx">foo</span><span class="o">:</span> <span class="s1">&#39;bar&#39;</span><span class="p">,</span>
      <span class="p">},</span>
      <span class="nx">fromEnv</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;CLUSTER&#39;</span><span class="p">,</span> <span class="s1">&#39;NODE_ENV&#39;</span><span class="p">],</span>
    <span class="p">},</span>
  <span class="p">}),</span>
  <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">req</span><span class="p">.</span><span class="nx">log</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s1">&#39;Request received to update user profile.&#39;</span><span class="p">)</span>
    <span class="c1">// ...</span>
  <span class="p">}</span>
<span class="p">]</span>
</pre></div>
<p>The <code>log.info</code> would emit to stdout an event that looked something like this:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
    <span class="nt">&quot;specversion&quot;</span> <span class="p">:</span> <span class="s2">&quot;1.0&quot;</span><span class="p">,</span>
    <span class="nt">&quot;type&quot;</span> <span class="p">:</span> <span class="s2">&quot;org.cncf.observable/msg&quot;</span><span class="p">,</span>
    <span class="nt">&quot;source&quot;</span> <span class="p">:</span> <span class="s2">&quot;com.myco/users&quot;</span><span class="p">,</span>
    <span class="nt">&quot;subject&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
    <span class="nt">&quot;id&quot;</span> <span class="p">:</span> <span class="s2">&quot;34dc3d61-6c71-4694-9c59-f354d327dce7&quot;</span><span class="p">,</span>
    <span class="nt">&quot;time&quot;</span> <span class="p">:</span> <span class="s2">&quot;2021-03-21T04:02:21.331Z&quot;</span><span class="p">,</span>
    <span class="nt">&quot;datacontenttype&quot;</span> <span class="p">:</span> <span class="s2">&quot;application/json&quot;</span><span class="p">,</span>
    <span class="nt">&quot;data&quot;</span> <span class="p">:</span> <span class="p">{</span>
        <span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="s2">&quot;34dc3d61-6c71-4694-9c59-f354d327dce7&quot;</span><span class="p">,</span>
        <span class="nt">&quot;time&quot;</span> <span class="p">:</span> <span class="s2">&quot;2021-03-21T04:02:21.331Z&quot;</span><span class="p">,</span>
        <span class="nt">&quot;level&quot;</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span>
        <span class="nt">&quot;component&quot;</span><span class="p">:</span> <span class="s2">&quot;UpdateUserProfile&quot;</span><span class="p">,</span>
        <span class="nt">&quot;message&quot;</span><span class="p">:</span> <span class="s2">&quot;Request received to update user profile.&quot;</span><span class="p">,</span>
        <span class="nt">&quot;tags&quot;</span><span class="p">:</span> <span class="p">{</span>
          <span class="nt">&quot;foo&quot;</span><span class="p">:</span> <span class="s2">&quot;bar&quot;</span><span class="p">,</span>
          <span class="nt">&quot;CLUSTER&quot;</span><span class="p">:</span> <span class="s2">&quot;staging&quot;</span><span class="p">,</span>
          <span class="nt">&quot;NODE_ENV&quot;</span><span class="p">:</span> <span class="s2">&quot;staging&quot;</span>
        <span class="p">},</span>
        <span class="nt">&quot;metadata&quot;</span><span class="p">:</span> <span class="p">{</span>
          <span class="nt">&quot;sessionId&quot;</span><span class="p">:</span> <span class="s2">&quot;121jkhuaiuuhaiusdh13989hasjkdfh1&quot;</span><span class="p">,</span>
          <span class="nt">&quot;params&quot;</span><span class="p">:</span> <span class="p">{</span>
            <span class="nt">&quot;userId&quot;</span><span class="p">:</span> <span class="mi">123</span><span class="p">,</span>
          <span class="p">},</span>
          <span class="nt">&quot;client&quot;</span><span class="p">:</span> <span class="p">{</span>
            <span class="nt">&quot;agent&quot;</span><span class="p">:</span> <span class="s2">&quot;Mozilla/5.0 (Macintosh; Intel Mac OS X...&quot;</span><span class="p">,</span>
            <span class="nt">&quot;ip&quot;</span><span class="p">:</span> <span class="s2">&quot;127.0.0.1&quot;</span>
          <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p><em>This is pretty-printed -- it would be one object per line.</em></p>

<p>Observability contexts originate using the common &quot;child&quot; convention.  Child contexts inherit metadata from the parent and can override/extend those properties with additional tags.  Child contexts are also a natural place to begin and end tracing spans automatically:</p>
<div class="highlight"><pre><span></span><span class="nx">async</span> <span class="kd">function</span> <span class="nx">getUserProfile</span><span class="p">(</span>
  <span class="nx">logger</span>: <span class="kt">Log</span><span class="p">,</span>
  <span class="nx">userId</span>: <span class="kt">number</span><span class="p">,</span>
<span class="p">)</span><span class="o">:</span> <span class="nx">Promise</span><span class="o">&lt;</span><span class="nx">Profile</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span> <span class="p">{</span>

   <span class="kr">const</span> <span class="nx">log</span> <span class="o">=</span> <span class="nx">logger</span><span class="p">.</span><span class="nx">child</span><span class="p">({</span>
     <span class="nx">component</span><span class="o">:</span> <span class="s1">&#39;getUserProfile&#39;</span><span class="p">,</span>
     <span class="nx">userId</span><span class="p">,</span>
   <span class="p">})</span>

   <span class="nx">log</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span><span class="s1">&#39;Looking up user profile.&#39;</span><span class="p">)</span>

   <span class="kr">const</span> <span class="nx">profile</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">repo</span><span class="p">.</span><span class="nx">getProfile</span><span class="p">(</span><span class="nx">userId</span><span class="p">)</span>

   <span class="nx">log</span><span class="p">.</span><span class="nx">debug</span><span class="p">(</span>
     <span class="p">{</span> <span class="nx">profileFound</span><span class="o">:</span> <span class="o">!!</span><span class="nx">profile</span> <span class="p">},</span>
     <span class="s1">&#39;Returning profile to caller.&#39;</span>
   <span class="p">)</span>

   <span class="c1">// It would be nicer to auto close this!</span>
   <span class="nx">log</span><span class="p">.</span><span class="nx">finish</span><span class="p">()</span>

   <span class="k">return</span> <span class="nx">profile</span>
<span class="p">}</span>

<span class="c1">// ...</span>
<span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
   <span class="c1">// ...</span>
   <span class="kr">const</span> <span class="nx">profile</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">getUserProfile</span><span class="p">(</span>
     <span class="nx">req</span><span class="p">.</span><span class="nx">log</span><span class="p">,</span>
     <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">userId</span><span class="p">,</span>
   <span class="p">)</span>
   <span class="c1">// ...</span>
<span class="p">}</span>
</pre></div>
<p>The code above might produce the following events:</p>
<div class="highlight"><pre><span></span><span class="p">[</span>
  <span class="p">{</span>
      <span class="nt">&quot;specversion&quot;</span> <span class="p">:</span> <span class="s2">&quot;1.0&quot;</span><span class="p">,</span>
      <span class="nt">&quot;type&quot;</span> <span class="p">:</span> <span class="s2">&quot;org.cncf.observable/msg&quot;</span><span class="p">,</span>
      <span class="nt">&quot;source&quot;</span> <span class="p">:</span> <span class="s2">&quot;com.myco/users&quot;</span><span class="p">,</span>
      <span class="nt">&quot;subject&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
      <span class="nt">&quot;id&quot;</span> <span class="p">:</span> <span class="s2">&quot;34dc3d61-6c71-4694-9c59-f354d327dce7:d1f358c60c4e:1&quot;</span><span class="p">,</span>
      <span class="nt">&quot;time&quot;</span> <span class="p">:</span> <span class="s2">&quot;2021-03-21T04:02:21.012Z&quot;</span><span class="p">,</span>
      <span class="nt">&quot;datacontenttype&quot;</span> <span class="p">:</span> <span class="s2">&quot;application/json&quot;</span><span class="p">,</span>
      <span class="nt">&quot;data&quot;</span> <span class="p">:</span> <span class="p">{</span>
          <span class="nt">&quot;level&quot;</span><span class="p">:</span> <span class="mi">20</span><span class="p">,</span>
          <span class="nt">&quot;component&quot;</span><span class="p">:</span> <span class="s2">&quot;getUserProfile&quot;</span><span class="p">,</span>
          <span class="nt">&quot;message&quot;</span><span class="p">:</span> <span class="s2">&quot;Looking up user profile.&quot;</span><span class="p">,</span>
          <span class="err">//</span> <span class="err">...metadata</span> <span class="err">and</span> <span class="err">tags</span>
       <span class="p">}</span>
  <span class="p">},</span>
  <span class="p">{</span>
      <span class="nt">&quot;specversion&quot;</span> <span class="p">:</span> <span class="s2">&quot;1.0&quot;</span><span class="p">,</span>
      <span class="nt">&quot;type&quot;</span> <span class="p">:</span> <span class="s2">&quot;org.cncf.observable/msg&quot;</span><span class="p">,</span>
      <span class="nt">&quot;source&quot;</span> <span class="p">:</span> <span class="s2">&quot;com.myco/users&quot;</span><span class="p">,</span>
      <span class="nt">&quot;subject&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
      <span class="nt">&quot;id&quot;</span> <span class="p">:</span> <span class="s2">&quot;34dc3d61-6c71-4694-9c59-f354d327dce7&quot;</span><span class="p">,</span>
      <span class="nt">&quot;time&quot;</span> <span class="p">:</span> <span class="s2">&quot;2021-03-21T04:02:21.212Z&quot;</span><span class="p">,</span>
      <span class="nt">&quot;datacontenttype&quot;</span> <span class="p">:</span> <span class="s2">&quot;application/json&quot;</span><span class="p">,</span>
      <span class="nt">&quot;data&quot;</span> <span class="p">:</span> <span class="p">{</span>
          <span class="nt">&quot;level&quot;</span><span class="p">:</span> <span class="mi">20</span><span class="p">,</span>
          <span class="nt">&quot;component&quot;</span><span class="p">:</span> <span class="s2">&quot;getUserProfile&quot;</span><span class="p">,</span>
          <span class="nt">&quot;message&quot;</span><span class="p">:</span> <span class="s2">&quot;Returning profile to caller.&quot;</span>
          <span class="err">//</span> <span class="err">...metadata</span> <span class="err">and</span> <span class="err">tags</span>
      <span class="p">}</span>
  <span class="p">},</span>
  <span class="p">{</span>
      <span class="nt">&quot;specversion&quot;</span> <span class="p">:</span> <span class="s2">&quot;1.0&quot;</span><span class="p">,</span>
      <span class="nt">&quot;type&quot;</span> <span class="p">:</span> <span class="s2">&quot;org.cncf.observable/ctx-end&quot;</span><span class="p">,</span>
      <span class="nt">&quot;source&quot;</span> <span class="p">:</span> <span class="s2">&quot;com.myco/users&quot;</span><span class="p">,</span>
      <span class="nt">&quot;subject&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
      <span class="nt">&quot;id&quot;</span> <span class="p">:</span> <span class="s2">&quot;34dc3d61-6c71-4694-9c59-f354d327dce7:d1f358c60c4e:3&quot;</span><span class="p">,</span>
      <span class="nt">&quot;time&quot;</span> <span class="p">:</span> <span class="s2">&quot;2021-03-21T04:02:21.323Z&quot;</span><span class="p">,</span>
      <span class="nt">&quot;datacontenttype&quot;</span> <span class="p">:</span> <span class="s2">&quot;application/json&quot;</span><span class="p">,</span>
      <span class="nt">&quot;data&quot;</span> <span class="p">:</span> <span class="p">{</span>
          <span class="err">//</span> <span class="err">...metadata</span> <span class="err">and</span> <span class="err">tags</span>
      <span class="p">}</span>
  <span class="p">}</span>
<span class="p">]</span>
</pre></div>
<p>The example events use a hierarchical ID to represent the context chain.  The original ID (a UUID) originated from the request middleware.  The last event&#39;s message type signifies the end of a context; this would be used by tracing systems to mark the end of a span.</p>

<h2 id="conclusion">Conclusion</h2>

<p>While observability tools (logging, metrics, tracing) continue to evolve, the developer experience and infrastructure continue to lag behind vendor solutions.  I believe the industry could make some minor changes towards a unified standard for emitting &quot;observability events&quot; that would be easier (and cleaner) to integrate into services and decouple aggregation infrastructure and vendor solutions.   This approach would benefit all parties by minimizing the complexity of using these tools and democratizing the interfaces so vendors and open-source providers could compete on a level playing field.</p>
]]></content:encoded>
      </item>
  </channel>
</rss>