<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>al=0x13</title>
  
  
  <link href="https://modethirteen.com/atom.xml" rel="self"/>
  
  <link href="https://modethirteen.com/"/>
  <updated>2020-11-25T05:18:51.000Z</updated>
  <id>https://modethirteen.com/</id>
  
  <author>
    <name>James Andrew Vaughn (Andy)</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Interfaces for Humans on an API-First Platform</title>
    <link href="https://modethirteen.com/2020/04/20/interfaces-for-humans-on-an-api-first-platform/"/>
    <id>https://modethirteen.com/2020/04/20/interfaces-for-humans-on-an-api-first-platform/</id>
    <published>2020-04-21T02:35:45.000Z</published>
    <updated>2020-11-25T05:18:51.000Z</updated>
    
    <content type="html"><![CDATA[<!-- markdownlint-disable no-inline-html --><p><img src="/2020/04/20/interfaces-for-humans-on-an-api-first-platform/photo-1536104968055-4d61aa56f46a.jpg" alt="Image"><span class="caption">Image: <a href="https://unsplash.com/@trueagency">True Agency</a></span></p><!-- markdownlint-enable no-inline-html --><p><em>Update 2020-11-21</em>: I particularly enjoyed <a href="https://www.arp242.net/api-ux.html">this post</a> by <a href="https://www.arp242.net/">Martin Tournoij</a>. I think he captures the type of empathy for developers that I struggled to express to the revenue-driven side of our business before I invested in my product management chops. Poorly designed and documented APIs contribute to (though are not entirely the cause of) poorly executed integrations - which is surely felt when they add risk to strategic partnerships. Back to regularly scheduled programming…</p><p>During my time at <a href="https://mindtouch.com/">MindTouch</a>, the early design decision to build an <a href="https://swagger.io/resources/articles/adopting-an-api-first-approach">API-first platform</a> has yielded some <em>amazing</em> technical gains that I benefited from as a software architect, but also some real pains that I experienced years later as a technical product manager positioning the same API for partner developers and system integrators.</p><p><a href="https://twitter.com/bjorg">Steve Bjorg</a>, Founder/CTO of MindTouch, explained the decisions around API-first in 2007…</p><div class="video-container"><iframe src="https://www.youtube.com/embed/XxSjNkkqyoo" frameborder="0" loading="lazy" allowfullscreen></iframe></div><p>In 2019, one of MindTouch’s principal engineers, <a href="https://twitter.com/onema">Juan Torres</a>, elaborated on the downstream benefits of MindTouch’s event-driven service model, which benefits greatly from Steve’s original API-first vision…</p><div class="video-container"><iframe src="https://www.youtube.com/embed/mxKhbU_ToMs" frameborder="0" loading="lazy" allowfullscreen></iframe></div><p>Steve’s original concept, combined with MindTouch’s strong open-source friendly outlook at the time, were the core reasons I joined the organization. However, as the years progressed and MindTouch began to reposition itself for more turn-key use cases, I sensed a lack of ownership over the developer experience, as IT system admin and integrator personas and needs lacked strong understanding or empathy from our organization. As a result, our engineers added new functionality to the shared API simply to facilitate the creation of new features in the product experience, with neither them nor product managers considering how these APIs could be leveraged directly to create value.</p><p>When I took on the challenge of positioning the API to solve partner and B2B customer integration problems, I found that years of neglect had led to poorly maintained documentation and inconsistency across API input parameters and response data structure patterns. API endpoints that were clearly only created for internal DevOps needs were publicly exposed. Access to these endpoints was fortunately controlled, but still resulted in a confusing experience for partner developers.</p><p>Unfortunately, due to lock-in with <a href="https://github.com/MindTouch/DReAM">our proprietary RESTful API framework</a>, we lacked some of the “auto-documenting” capabilities of tools like <a href="https://swagger.io/">Swagger</a>. After building similar capabilities into our framework, it was time to enforce some sort of standard for API visibility and documentation. I wrote and shared the following document with our engineering team to reintroduce this discipline. I’m sharing this document publicly to demonstrate a level of control a product manager may need to place on the interface, to provide a consistent <em>documentation</em> experience. After API parameters and descriptions are documented consistently and intuitively, the natural next step is to apply standards (possibly in the form of a design spec) on the input and output data structures themselves.</p><p>A UI/UX designer considers intuitiveness and consistency with their approach with graphical user interfaces, why not take the same approach with a developer’s interface, an API?</p><hr><h2 id="API-Feature-Development"><a href="#API-Feature-Development" class="headerlink" title="API Feature Development"></a>API Feature Development</h2><p>As a <a href="https://success.mindtouch.com/Integrations/API">product capability</a>, the public API requires consistency in its presentation to customers and partner developers. Following these conventions and best practices ensures that the public API documentation and experience will remain consistently high quality.</p><h3 id="API-Visibility"><a href="#API-Visibility" class="headerlink" title="API Visibility"></a>API Visibility</h3><p>All new API features (endpoints) are initially created in one of two allowed states: <em>hidden</em> or <em>internal</em>. Neither hidden nor internal features appear in API documentation. Depending on how permissions are checked, hidden features can be accessible by any user type or role, whereas internal features can only be access by customer site administrators or internal MindTouch staff.</p><p>With few exceptions, new features <em>require</em> token validation and include an attribute to ensure that <em>all</em> sites enforce this behavior on these features. This includes older sites that do not yet have token validation enforcement enabled as the default behavior for <em>all</em> API features.</p><p>Some internal features are intended to only be used by Engineering, DevOps, and Support whilst on the MindTouch employee network or VPN. These features are marked as <em>trusted internal features</em> with an attribute.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Hidden features have a public visibility keyword, so that they can</span></span><br><span class="line"><span class="comment">// be accessed by non-administrators in user experience, however the</span></span><br><span class="line"><span class="comment">// feature remains hidden in API documentation until it is reviewed by</span></span><br><span class="line"><span class="comment">// the API product manager.</span></span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;GET:widgets&quot;</span>, <span class="meta-string">&quot;Retrieve list of widgets&quot;</span>, Hidden = true)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// Here is the one use case for disabling authorization or API token</span></span><br><span class="line"><span class="comment">// enforcement: downloading log reports, images, file attachments, etc.</span></span><br><span class="line"><span class="comment">// Browsers download these resources as media files and will not send</span></span><br><span class="line"><span class="comment">// authorization if the user is anonymous.</span></span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;GET:files/&#123;filename&#125;&quot;</span>, <span class="meta-string">&quot;Download file&quot;</span>)</span>]</span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;GET:users.csv&quot;</span>, <span class="meta-string">&quot;Download list of users&quot;</span>)</span>]</span><br><span class="line">[<span class="meta">TokenNotRequiredFeature</span>]</span><br><span class="line"><span class="function"><span class="keyword">public</span> DreamMessage <span class="title">GetFile</span>(<span class="params">IAttachmentsBL attachmentsBL</span>)</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Internal features have a internal visibility keyword and can only be</span></span><br><span class="line"><span class="comment">// accessed by internal MindTouch staff or customer site administrators.</span></span><br><span class="line"><span class="comment">// Internal features are intended to never be visible in API documentation.</span></span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;PUT:site&quot;</span>, <span class="meta-string">&quot;Destroy a site&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// Add this attribute if the internal feature is intended by used internally</span></span><br><span class="line"><span class="comment">// by DevOps</span></span><br><span class="line">[<span class="meta">TrustedInternalFeature</span>]</span><br><span class="line"><span class="function"><span class="keyword">internal</span> DreamMessage <span class="title">PutSite</span>(<span class="params">INukeBL nukeBL</span>)</span> &#123; &#125;</span><br></pre></td></tr></table></figure><h3 id="API-Feature-Descriptions"><a href="#API-Feature-Descriptions" class="headerlink" title="API Feature Descriptions"></a>API Feature Descriptions</h3><p>API descriptions use common semantics and phrases to articulate what they are used for. While these are the general rule of thumb, exceptions can be made for XML-RPC style features (move, copy, etc), that do not follow RESTful conventions. As necessary with any product messaging, the API product manager will assist you in providing the correct phrasing and tone.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GET features &quot;retrieve&quot; resources.</span></span><br><span class="line"><span class="comment">// A feature that retrieves more than one resource, retrieves a &quot;list&quot;.</span></span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;GET:widgets&quot;</span>, <span class="meta-string">&quot;Retrieve list of widgets&quot;</span>)</span>]</span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;GET:widget/&#123;id&#125;&quot;</span>, <span class="meta-string">&quot;Retrieve widget&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// GET features &quot;download&quot; resources if they have a</span></span><br><span class="line"><span class="comment">// &quot;Content-Disposition&quot; header</span></span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;GET:files/&#123;filename&#125;&quot;</span>, <span class="meta-string">&quot;Download file&quot;</span>)</span>]</span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;GET:users.csv&quot;</span>, <span class="meta-string">&quot;Download list of users&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// PUT features &quot;update&quot; a resource.</span></span><br><span class="line"><span class="comment">// The resource is never prefixed with an article (&quot;a&quot;, &quot;an&quot;, &quot;the&quot;).</span></span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;PUT:widgets/&#123;id&#125;&quot;</span>, <span class="meta-string">&quot;Update widget&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// POST features &quot;create&quot; or &quot;create or update&quot; a resource</span></span><br><span class="line"><span class="comment">// (depending on the behavior of the method).</span></span><br><span class="line"><span class="comment">// The resource is never prefixed with an article (&quot;a&quot;, &quot;an&quot;, &quot;the&quot;).</span></span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;POST:widgets&quot;</span>, <span class="meta-string">&quot;Create widget&quot;</span>)</span>]</span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;POST:widgets&quot;</span>, <span class="meta-string">&quot;Create or update widget&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// DELETE features &quot;remove&quot; a resource.</span></span><br><span class="line"><span class="comment">// The resource is never prefixed with an article (&quot;a&quot;, &quot;an&quot;, &quot;the&quot;).</span></span><br><span class="line">[<span class="meta">DreamFeature(<span class="meta-string">&quot;DELETE:widgets/&#123;id&#125;&quot;</span>, <span class="meta-string">&quot;Remove widget&quot;</span>)</span>]</span><br></pre></td></tr></table></figure><h3 id="API-Feature-Parameters"><a href="#API-Feature-Parameters" class="headerlink" title="API Feature Parameters"></a>API Feature Parameters</h3><p>API feature parameters also require consistency in phrasing and tone. Several parameters such as <code>&#123;pageid&#125;</code>, <code>&#123;fileid&#125;</code>, <code>&#123;userid&#125;</code>, and <code>&#123;groupid&#125;</code> are reused through different features, and are resolved from the request in a common way (ex: <code>pageid</code> can be an integer, “home”, a <code>=</code> prefixed title, or a <code>:</code> prefixed GUID).</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">DreamFeature(</span></span><br><span class="line"><span class="meta">    <span class="meta-string">&quot;GET:pages/&#123;pageid&#125;/files/&#123;filename&#125;&quot;</span>,</span></span><br><span class="line"><span class="meta">    <span class="meta-string">&quot;Retrieve page file attachment&quot;</span></span></span><br><span class="line"><span class="meta">)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// there are constant descriptions for common parameters</span></span><br><span class="line">[<span class="meta">DreamFeatureParam(<span class="meta-string">&quot;&#123;pageid&#125;&quot;</span>, <span class="meta-string">&quot;string&quot;</span>, PARAM_PAGEID_DESCRIPTION)</span>]</span><br><span class="line">[<span class="meta">DreamFeatureParam(<span class="meta-string">&quot;&#123;filename&#125;&quot;</span>, <span class="meta-string">&quot;string&quot;</span>, PARAM_FILENAME_DESCRIPTION)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// parameter descriptions never end with a &quot;.&quot; character</span></span><br><span class="line">[<span class="meta">DreamFeatureParam(<span class="meta-string">&quot;description&quot;</span>, <span class="meta-string">&quot;string?&quot;</span>, <span class="meta-string">&quot;File attachment description&quot;</span>)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// default values on optional values are expressed with (default: &#123;value&#125;)</span></span><br><span class="line">[<span class="meta">DreamFeatureParam(</span></span><br><span class="line"><span class="meta">    <span class="meta-string">&quot;limit&quot;</span>, <span class="meta-string">&quot;int?&quot;</span>, <span class="meta-string">&quot;File attachment download size limit (default: 2048)&quot;</span></span></span><br><span class="line"><span class="meta">)</span>]</span><br></pre></td></tr></table></figure><h3 id="Obsolete-API-Features"><a href="#Obsolete-API-Features" class="headerlink" title="Obsolete API Features"></a>Obsolete API Features</h3><p>Obsoleting a feature means either a different feature should be used, or the feature is sunsetted entirely. Marking a feature obsolete has the same effect as hiding it, so the Hidden property is redundant.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// directing users to a new feature is expressed by &quot;Use &#123;feature&#125;&quot; in</span></span><br><span class="line"><span class="comment">// the Obsolete property.</span></span><br><span class="line">[<span class="meta">DreamFeature(</span></span><br><span class="line"><span class="meta">    <span class="meta-string">&quot;GET:site/widgets&quot;</span>,</span></span><br><span class="line"><span class="meta">    <span class="meta-string">&quot;Retrieve list of widgets&quot;</span>,</span></span><br><span class="line"><span class="meta">    Obsolete = <span class="meta-string">&quot;Use GET:widgets&quot;</span></span></span><br><span class="line"><span class="meta">)</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// the method for a sunsetted feature is also marked with an</span></span><br><span class="line"><span class="comment">// ObsoleteAttribute, and an API product manager approved sunset</span></span><br><span class="line"><span class="comment">// message is set in the Obsolete property.</span></span><br><span class="line">[<span class="meta">Obsolete</span>]</span><br><span class="line">[<span class="meta">DreamFeature(</span></span><br><span class="line"><span class="meta">    <span class="meta-string">&quot;GET:widgets&quot;</span>,</span></span><br><span class="line"><span class="meta">    <span class="meta-string">&quot;Retrieve list of widgets&quot;</span>,</span></span><br><span class="line"><span class="meta">    Obsolete = <span class="meta-string">&quot;Widgets have been sunsetted are no longer available&quot;</span></span></span><br><span class="line"><span class="meta">)</span>]</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">APIs are for people too, so let&#39;s stop treating developers like machines.</summary>
    
    
    
    
    <category term="Product Management" scheme="https://modethirteen.com/tags/Product-Management/"/>
    
    <category term="C Sharp" scheme="https://modethirteen.com/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>You May Not Need Service Discovery</title>
    <link href="https://modethirteen.com/2019/12/23/you-may-not-need-service-discovery/"/>
    <id>https://modethirteen.com/2019/12/23/you-may-not-need-service-discovery/</id>
    <published>2019-12-24T02:46:30.000Z</published>
    <updated>2020-11-05T19:06:55.000Z</updated>
    
    <content type="html"><![CDATA[<!-- markdownlint-disable no-inline-html --><p><img src="/2019/12/23/you-may-not-need-service-discovery/photo-1597733336794-12d05021d510.jpg" alt="Image"><span class="caption">Image: <a href="https://unsplash.com/@jjying">JJ Ying</a></span></p><!-- markdownlint-enable no-inline-html --><p>As modern application architecture is decoupled more and more into independently running services, we’re all once again experiencing the pains that some of us remember from the <a href="https://en.wikipedia.org/wiki/Service-oriented_architecture">SOA</a> days in the early 2000s. A popular saying in software engineering is that the two hardest problems in computer science are:</p><ol><li>Cache invalidation</li><li>Naming things</li></ol><p>…and then everyone typically adds a third problem, based on whatever particular stress they are going through at that very moment:</p><!-- markdownlint-disable no-bare-urls --><div class="twitter-wrapper"><blockquote class="twitter-tweet"><a href="https://twitter.com/codinghorror/status/506010907021828096"></a></blockquote></div><script async defer src="//platform.twitter.com/widgets.js" charset="utf-8"></script><!-- markdownlint-enable no-bare-urls --><p>As of this year, my “number three” on the list is:</p><!-- markdownlint-disable ol-prefix --><ol start="3"><li>Constantly having to remind myself that in-memory communication between software components is <em>always</em> easier than over the wire<!-- markdownlint-enable ol-prefix --></li></ol><p>My inner-voice is there to convince me that breaking a component off of the <em>monolith</em> (that component being what we are all calling a <em>microservice</em> now) probably introduces a range of new problems that can overshadow the original perceived value of decomposing the application in the first place. Even load-balanced monolithic application servers have SOA-like problems that are still there even if the monolith goes away. At <a href="https://mindtouch.com/">MindTouch</a>, we ran into one that had been hiding in plain sight for nearly a decade.</p><p>We’ve used a <em>progressive</em> <a href="https://en.wikipedia.org/wiki/Blue-green_deployment">blue-green deployment</a> for many years to release new software on our shared, multi-tenant SaaS infrastructure. The approach is progressive in-so-far that tenants are switched to new software in batches (to monitor for problems and lessen their impact) as opposed to flipping a switch for all tenants at once.</p><p><img src="/2019/12/23/you-may-not-need-service-discovery/image21.png" alt="Image"></p><p>Tenants are identified by hostname (ex: <code>foo.mindtouch.us</code>, <code>bar.mindtouch.us</code>, etc). Incoming requests are routed to the appropriate pool of EC2-hosted application servers for the requested tenant. If a blue-green deployment is in progress, and the tenant is queued to switch to the new software release but has not yet been moved over to it, the load balancer can still route correctly. This is due to the presence of a site configuration authority: a centralized database of all tenants and their deployments, plus an internal tenant manager application that writes to this database. Deployments are simply a list of EC2 IP addresses that represent the application servers in a particular pool.</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">&quot;release_20130620&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;161.21.53.204&quot;</span>,</span><br><span class="line">        <span class="string">&quot;178.3.205.241&quot;</span>,</span><br><span class="line">        <span class="string">&quot;211.244.229.69&quot;</span>,</span><br><span class="line">        <span class="string">&quot;137.62.44.231&quot;</span></span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">&quot;release_20130617&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;210.124.193.170&quot;</span>,</span><br><span class="line">        <span class="string">&quot;177.113.206.133&quot;</span>,</span><br><span class="line">        <span class="string">&quot;222.179.114.209&quot;</span>,</span><br><span class="line">        <span class="string">&quot;7.147.241.158&quot;</span></span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="/2019/12/23/you-may-not-need-service-discovery/image22.png" alt="Image"></p><p>Blue-green can certainly mitigate risk in this “load-balanced application server” architecture. Any software update, regardless of whatever model or methodology you use, will always introduce risk. This particular approach makes it tolerable for us and our users. Due to the requirements that we had when standing up this infrastructure, we chose <a href="http://www.haproxy.org/">HAProxy</a> over Amazon’s native <a href="https://aws.amazon.com/elasticloadbalancing">load balancer</a>. This meant that while we <em>could</em> place these application servers in <a href="https://aws.amazon.com/autoscaling">auto-scaling groups</a>, there wouldn’t be any benefit if our load balancer of choice and our home-grown deployment model couldn’t take advantage of it. If our service ever came under heavy load, we would have to spin-up EC2s manually, provision them with all the requirements to run our software (and install the application software itself) using <a href="https://puppet.com/">Puppet</a>, then add the EC2 IP address to the application server pool for the live deployment. Historically, this was required so infrequently that automating these steps didn’t seem necessary.</p><p>Until we had a real SaaS business going…</p><p>This year we completed a migration of all application servers and event-driven microservices to containers hosted on <a href="https://aws.amazon.com/eks">Amazon EKS</a> (k8s). The many reasons behind the switch are deserving of a case study, but the key point that’s relevant to this post is that we could <em>quickly</em> auto-scale application servers (now in k8s <em>pods</em>) <em>and</em> it was becoming necessary to do so. As an added benefit, all load balancing between deployments could be managed within k8s, making the old deployment model of static IP address lists obsolete.</p><p>There was one big problem. The fore-mentioned internal tenant manager can <em>restart</em> tenants - which is required for certain kinds of configuration changes. Each application server, in every container, keeps an in-memory record of the configurations for every tenant that it’s had to serve. Keeping an in-memory copy allows any application server to quickly handle a request for any tenant (so specific tenants need not be pegged to specific containers). In order to restart a tenant, the tenant manager would send a restart HTTP payload to an internal API endpoint on each application server. With k8s managing and auto-scaling all containers, this application had no idea which IP addresses to target - effectively breaking this capability.</p><blockquote><p>We need service discovery!</p><footer><strong>Everyone ever with a one to many or many to many networked service communication problem</strong></footer></blockquote><p>The faulty assumption, out of the gate, is that this tenant manager still needs to know <em>where</em> the downstream application is running. When faced with challenges, we can often gravitate towards what is known or what we are comfortable with. We presume that other options are going to be difficult to achieve. Why not? We obviously chose the simplest option when we first built this! With that presumption in place, we are likely ignoring an <em>even simpler</em> and possibly more elegant solution - because in the last few years we’ve become smarter and better at our craft.</p><p>All the tenant manager needs to know about is one location: where to write a big message on the side of a wall for someone to read later. The message is for any application server that reads it to restart a specific tenant - and every application server knows to check the wall before handling an incoming request, just in case there is new information about the intended tenant for the request. We implemented this with a <em>distributed lifecycle token</em>.</p><p>A distributed lifecycle token is an arbitrary value stored in an authoritative location that downstream applications and services can use to detect upstream changes. If downstream components store the value of the token and later detect that their stored value no longer matches the authoritative source, they can infer some sort of meaning from that. In our use case, they know that the tenant manager has requested the restart of a tenant. Allowing downstream components to <em>eventually</em> take action on, or be triggered by, a write to a data store, an event stream, or a similar authoritative location is a great example of event-driven and <a href="https://en.wikipedia.org/wiki/Reactive_programming">reactive programming</a> in use.</p><p>First, we need a connection to a centralized, in-memory data store (such as <a href="https://memcached.org/">Memcached</a>, <a href="https://redis.io/">Redis</a>, etc.) that can be accessed by any application server. Any tokens in this data store need to contain some sort of tenant-specific context. The following example is <em>not</em> meant to be representative of a full-featured Memcached client, but rather the minimum required to demonstrate this implementation.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">MemcachedClient</span> : <span class="title">IMemcachedClient</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Fields ---</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> IMemcachedClient _client;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="built_in">string</span> _tenantId;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Methods ---</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">MemcachedClient</span>(<span class="params"><span class="built_in">string</span> tenantId, IMemcachedClient client</span>)</span> &#123;</span><br><span class="line">        _client = client;</span><br><span class="line">        _siteId = siteId;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">object</span> <span class="title">Get</span>(<span class="params"><span class="built_in">string</span> key</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> _client.Get(NormalizeKey(key));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> IDictionary&lt;<span class="built_in">string</span>, <span class="built_in">object</span>&gt; <span class="title">Get</span>(<span class="params">IEnumerable&lt;<span class="built_in">string</span>&gt; keys</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> result = _client.Get(keys.Select(NormalizeKey).ToArray());</span><br><span class="line">        <span class="keyword">return</span> result.ToDictionary(kv =&gt; DenormalizeKey(kv.Key), kv =&gt; kv.Value);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">string</span> <span class="title">NormalizeKey</span>(<span class="params"><span class="built_in">string</span> key</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> String.Format(<span class="string">&quot;&#123;0&#125;:&#123;1&#125;&quot;</span>, _siteId, key);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">string</span> <span class="title">DenormalizeKey</span>(<span class="params"><span class="built_in">string</span> key</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> key.Substring(_siteId.Length + <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Next, we have libraries that manage the initialization and fetching of tokens using our tenant-contextual Memcached client.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title">IDistributedTokenProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Methods ---</span></span><br><span class="line">    <span class="function">IDictionary&lt;<span class="built_in">string</span>, <span class="built_in">string</span>&gt; <span class="title">GetDistributedTokens</span>(<span class="params">IEnumerable&lt;<span class="built_in">string</span>&gt; keys</span>)</span>;</span><br><span class="line">    <span class="function"><span class="built_in">string</span> <span class="title">GetDistributedToken</span>(<span class="params"><span class="built_in">string</span> key</span>)</span>;</span><br><span class="line">    <span class="function"><span class="built_in">string</span> <span class="title">InitToken</span>(<span class="params"><span class="built_in">string</span> key</span>)</span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">DistributeToken</span>(<span class="params"><span class="built_in">string</span> key, <span class="built_in">string</span> token, TimeSpan ttl</span>)</span>;</span><br><span class="line">    <span class="function"><span class="built_in">string</span> <span class="title">InitDistributedToken</span>(<span class="params"><span class="built_in">string</span> key, TimeSpan ttl</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DistributedTokenProvider</span> : <span class="title">IDistributedTokenProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Fields ---</span></span><br><span class="line">    <span class="keyword">private</span> IMemcachedClient _memcache;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Constructors ---</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">DistributedTokenProvider</span>(<span class="params">IMemcachedClient memcache</span>)</span> &#123;</span><br><span class="line">        _memcache = memcache;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Methods ---</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> IDictionary&lt;<span class="built_in">string</span>, <span class="built_in">string</span>&gt; <span class="title">GetDistributedTokens</span>(<span class="params">IEnumerable&lt;<span class="built_in">string</span>&gt; keys</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(keys.Count() == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> Dictionary&lt;<span class="built_in">string</span>, <span class="built_in">string</span>&gt;();</span><br><span class="line">        &#125;</span><br><span class="line">        keys = keys.Distinct().ToArray();</span><br><span class="line">        <span class="keyword">var</span> result = _memcache.Get(keys);</span><br><span class="line">        <span class="keyword">return</span> result.ToDictionary(kv =&gt; kv.Key, kv =&gt; SysUtil.ChangeType&lt;<span class="built_in">string</span>&gt;(kv.Value));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">GetDistributedToken</span>(<span class="params"><span class="built_in">string</span> key</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> _memcache.Get&lt;<span class="built_in">string</span>&gt;(key);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">InitToken</span>(<span class="params"><span class="built_in">string</span> key</span>)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// we have an internal utility for generating random memcache-friendly key strings,</span></span><br><span class="line">        <span class="comment">// point is, make the key safe and unique</span></span><br><span class="line">        <span class="keyword">return</span> key + <span class="string">&quot;:&quot;</span> + StringUtil.CreateAlphaNumericKey(<span class="number">8</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">DistributeToken</span>(<span class="params"><span class="built_in">string</span> key, <span class="built_in">string</span> token, TimeSpan ttl</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> expires = GlobalClock.UtcNow + ttl;</span><br><span class="line">        <span class="keyword">if</span>(expires != DateTime.MaxValue) &#123;</span><br><span class="line">            _memcache.Store(StoreMode.Set, key, token, expires);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            _memcache.Store(StoreMode.Set, key, token);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">InitDistributedToken</span>(<span class="params"><span class="built_in">string</span> key, TimeSpan ttl</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> token = InitToken(key);</span><br><span class="line">        DistributeToken(key, token, ttl);</span><br><span class="line">        <span class="keyword">return</span> token;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Next, we wrap our general-purpose distributed token logic in an easy to call utility. We can use this both in the tenant manager as well as the downstream application servers.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">TenantLifecycleTokenProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Class Fields ---</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">const</span> <span class="built_in">string</span> TOKEN_KEY = <span class="string">&quot;TOKEN_TENANT&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> TimeSpan TOKEN_TTL = TimeSpan.FromSeconds(<span class="number">60</span> * <span class="number">60</span> * <span class="number">24</span> * <span class="number">7</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Fields ---</span></span><br><span class="line">    <span class="keyword">private</span> IDistributedTokenProvider _tokenProvider;</span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">string</span> _token = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Constructors ---</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">TenantLifecycleTokenProvider</span>(<span class="params">IDistributedTokenProvider tokenProvider</span>)</span> &#123;</span><br><span class="line">        _tokenProvider = tokenProvider;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//--- Methods ---</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">GetToken</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(_token == <span class="literal">null</span>) &#123;</span><br><span class="line">            _token = _tokenProvider.GetDistributedToken(TOKEN_KEY);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> _token;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">InitToken</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">        _token = _tokenProvider.InitDistributedToken(TOKEN_KEY, TOKEN_TTL);</span><br><span class="line">        <span class="keyword">return</span> _token;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Instead of sending an HTTP payload to different IP addresses in a pool of application servers, now the tenant manager simply does this:</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> tenantId = <span class="string">&#x27;12345&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> lifecycleProvider = <span class="keyword">new</span> TenantLifecycleTokenProvider(_tokenFactory.NewDistributedTokenProvider(tenantId));</span><br><span class="line">lifecycleProvider.InitToken();</span><br></pre></td></tr></table></figure><p>A downstream application server, when it handles an incoming request for a tenant, first checks if it can lookup an in-memory copy of the tenant’s configuration. If not, it fetches it from the site configuration authority <em>and</em> looks up the tenant’s lifecycle token. If the lifecycle token for this tenant is not yet initialized (it may not have been if the tenant manager didn’t need to restart the tenant), then the application server initialize it, so that other application servers in the pool can know the current lifecycle state of the tenant.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> tenantId = <span class="string">&#x27;12345&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> tenantConfiguration = siteConfigurationAuthorityConnector.getTenantConfiguration(tenantId);</span><br><span class="line"><span class="keyword">var</span> lifecycleToken = lifecycleTokenProvider.GetToken();</span><br><span class="line"><span class="keyword">if</span>(lifecycleToken == <span class="literal">null</span>) &#123;</span><br><span class="line">    lifecycleToken = lifecycleTokenProvider.InitToken();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> tenant = <span class="keyword">new</span> Tenant(tenantConfiguration, lifecycleToken);</span><br><span class="line">tenants.Add(tenant.Id, tenant);</span><br></pre></td></tr></table></figure><p>When handling an incoming request, the application server checks if the tenant needs to be restarted before fulfilling the request. If the distributed lifecycle token has changed since we last read it and no longer matches the tenant’s lifecycle token, we know that the tenant manager is asking for a tenant restart.</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> tenantId = <span class="string">&#x27;12345&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> tenant = tenant.Get(tenantId);</span><br><span class="line"><span class="keyword">var</span> lifecycleToken = lifecycleTokenProvider.GetToken();</span><br><span class="line"><span class="keyword">if</span>(tenant.LifecycleToken != lifecycleToken) &#123;</span><br><span class="line">    tenant = <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In our particular architecture, nullifying the tenant at this stage triggers the application server to instantiate a new <code>Tenant</code> object and register it in the lookup, as seen above. This process is repeated for all application servers. The addition or reduction of containers or pods does not affect the tenant manager’s ability to restart tenants.</p><p>I am absolutely <em>not</em> a critic of service discovery in principal. When necessary to facilitate <em>direct communication</em> with components on a network, it can be a life-saver. However, if your use case does not require direct communication and eventual consistency among downstream data, and application state is acceptable for your use case, consider reactive programming solutions. Everything will be ok (eventually)!</p><p>…and if it’s not, you can always deploy systems to handle those situations out-of-band:</p><div class="video-container"><iframe src="https://www.youtube.com/embed/mxKhbU_ToMs" frameborder="0" loading="lazy" allowfullscreen></iframe></div>]]></content>
    
    
    <summary type="html">A consideration for distributed lifecycle tokens...</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="DevOps" scheme="https://modethirteen.com/tags/DevOps/"/>
    
    <category term="C Sharp" scheme="https://modethirteen.com/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>Impact Mapping with Graphviz</title>
    <link href="https://modethirteen.com/2019/10/16/impact-mapping-with-graphviz/"/>
    <id>https://modethirteen.com/2019/10/16/impact-mapping-with-graphviz/</id>
    <published>2019-10-16T20:12:10.000Z</published>
    <updated>2020-12-01T05:45:00.000Z</updated>
    
    <content type="html"><![CDATA[<!-- markdownlint-disable no-inline-html --><p><img src="/2019/10/16/impact-mapping-with-graphviz/0_e8YPUHOfr9xyFlkO.jpg" alt="Image"><span class="caption">Image: <a href="https://unsplash.com/@stay_in_touch">Patrick Perkins</a></span></p><!-- markdownlint-enable no-inline-html --><p>So I’ve turned on <em>terminator vision</em> lately while I try to locate the absolute unicorn of opportunities to prioritize in our product backlog. I’ve turned this into a borderline obsession now, after reading <a href="https://www.goodreads.com/book/show/24737268-badass">Kathy Sierra’s Badass: Making Users Awesome</a>. What is the absolutely smallest improvement that can have the greatest impact?</p><p>No, really, WHAT IS IT?!</p><p>A colleague of mine recently turned me onto a process that potentially gets me closer to that answer: <a href="https://www.impactmapping.org/">Impact Mapping</a>. I’ll let the creator, <a href="https://gojko.net/about">Gojko Adzic</a>, explain the process in detail <a href="https://www.impactmapping.org/book.html">in his highly recommended book</a>. The key factor here is that the collaborative process involves <em>building a graph</em> or a <em>tree</em> from which the smallest yet most impactful items can be recognized. In other words, you can visualize what is valuable and very importantly, things that are <em>not</em>. Suddenly, everyone sees the loudest and most highly paid individual’s “must-have” idea orphaned on a dead tree branch with wilting leaves. The concept of graphing value as dependencies (because I live for <a href="/2014/02/25/automocking-dependencies/" title="scalable dependency management">scalable dependency management</a>) is highly appealing to the engineer in me since my IDE these days is Google Slides 🙄.</p><p>Without getting too deep into the <a href="https://www.impactmapping.org/drawing.html">methodology of drawing impact maps</a>, let’s state that the map starts with a measurable product or company objective. The next level down includes the <em>actors</em>: the direct practitioners or indirect influencers that affect this goal, what actions they perform or impact that is created through their behavior, and what we need to deliver to enable (or in some cases secure against) those impacts.</p><p>Great - here is a possible impact map (using an example from the book), with success measurements, in <a href="https://graphviz.org/">Graphviz-compatible</a> <code>dot</code> format:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line">graph &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// draw the map from right to left</span></span><br><span class="line">    graph [rankdir=RL, nodesep=<span class="number">.5</span>, ranksep=<span class="number">.5</span>];</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Measureable Objective(s)</span></span><br><span class="line">    <span class="string">&quot;More Players&quot;</span> [style=filled, fillcolor=chartreuse]</span><br><span class="line">    subgraph cluster_Measurements &#123;</span><br><span class="line">        node [ shape=record ]</span><br><span class="line">        <span class="string">&quot;More Players&quot;</span> &#123;</span><br><span class="line">            <span class="string">&quot;What: Active monthly count&quot;</span></span><br><span class="line">            <span class="string">&quot;Where: Game database&quot;</span></span><br><span class="line">            <span class="string">&quot;Current: 350k&quot;</span></span><br><span class="line">            <span class="string">&quot;Target: 800k&quot;</span></span><br><span class="line">            <span class="string">&quot;Stretch Goal: 1M&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Associate actors with objectives</span></span><br><span class="line">    subgraph cluster_Actors &#123;</span><br><span class="line">        node [style=filled, fillcolor=cyan]</span><br><span class="line">        <span class="string">&quot;More Players&quot;</span> -- &#123;</span><br><span class="line">            <span class="string">&quot;Players&quot;</span></span><br><span class="line">            <span class="string">&quot;Internal&quot;</span></span><br><span class="line">            <span class="string">&quot;Advertisers&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// What are all the impacts that an advertiser can make?</span></span><br><span class="line">    subgraph AdvertiserImpacts &#123;</span><br><span class="line">        node [style=filled, fillcolor=cornflowerblue]</span><br><span class="line">        <span class="string">&quot;Advertisers&quot;</span> -- &#123;</span><br><span class="line">            <span class="string">&quot;Publishing our banners&quot;</span></span><br><span class="line">            <span class="string">&quot;Bulk invitations&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// What are all the impacts that internal staff can make?</span></span><br><span class="line">    subgraph AdvertiserImpacts &#123;</span><br><span class="line">        node [style=filled, fillcolor=brown1]</span><br><span class="line">        <span class="string">&quot;Internal&quot;</span> -- &#123;</span><br><span class="line">            <span class="string">&quot;Organize PR event&quot;</span></span><br><span class="line">            <span class="string">&quot;Engaging our network&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// What are all the impacts that a player can take?</span></span><br><span class="line">    subgraph PlayerImpacts &#123;</span><br><span class="line">        node [style=filled, fillcolor=deepskyblue]</span><br><span class="line">        <span class="string">&quot;Players&quot;</span> -- &#123;</span><br><span class="line">            <span class="string">&quot;Posting&quot;</span></span><br><span class="line">            <span class="string">&quot;Recommending&quot;</span></span><br><span class="line">            <span class="string">&quot;Inviting friends&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Which deliverables exist to organize events?</span></span><br><span class="line">    subgraph AdvertiserOrganizeEventOpportunities &#123;</span><br><span class="line">        <span class="string">&quot;Organize PR event&quot;</span> -- <span class="string">&quot;Invites&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Which deliverables impact the player posting experience?</span></span><br><span class="line">    subgraph PlayerPostingOpportunities &#123;</span><br><span class="line">        <span class="string">&quot;Posting&quot;</span> -- <span class="string">&quot;Content to post about&quot;</span></span><br><span class="line">        <span class="string">&quot;Content to post about&quot;</span> -- &#123;</span><br><span class="line">            <span class="string">&quot;Levels&quot;</span></span><br><span class="line">            <span class="string">&quot;Achievements&quot;</span></span><br><span class="line">            <span class="string">&quot;Weekly competitions&quot;</span></span><br><span class="line">            <span class="string">&quot;Tournament ending&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Which deliverables impact the player invitation experience?</span></span><br><span class="line">    subgraph PlayerInviteFriendsOpportunities &#123;</span><br><span class="line">        <span class="string">&quot;Inviting friends&quot;</span> -- &#123;</span><br><span class="line">            <span class="string">&quot;Semi-automated invites&quot;</span></span><br><span class="line">            <span class="string">&quot;Incentives&quot;</span></span><br><span class="line">            <span class="string">&quot;Personalization&quot;</span></span><br><span class="line">            <span class="string">&quot;More compelling product&quot;</span></span><br><span class="line">            <span class="string">&quot;Viral content&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="string">&quot;Incentives&quot;</span> -- &#123;</span><br><span class="line">            <span class="string">&quot;Chips&quot;</span></span><br><span class="line">            <span class="string">&quot;Recognition for inviting lots of friends&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="string">&quot;Personalization&quot;</span> -- &#123;</span><br><span class="line">            <span class="string">&quot;My tournaments&quot;</span></span><br><span class="line">            <span class="string">&quot;My table&quot;</span></span><br><span class="line">            <span class="string">&quot;My events&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="string">&quot;More compelling product&quot;</span> -- &#123;</span><br><span class="line">            <span class="string">&quot;Rebranding games&quot;</span></span><br><span class="line">            <span class="string">&quot;Website optimized for new users&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>…and here is the rendered graph. The <code>dot</code> file can be checked into a project repository as source code, to be updated if new information is received or requirements change.</p><p><img src="/2019/10/16/impact-mapping-with-graphviz/graph_lr.png" alt="Image"></p><p>Obviously, more work can go into arranging the components for a more pleasing layout, such as the example provided by the author:</p><!-- markdownlint-disable no-inline-html --><p><img src="/2019/10/16/impact-mapping-with-graphviz/gaming_example.png" alt="Image"><span class="caption">Image: <a href="https://www.impactmapping.org/example.html">https://impactmapping.org</a></span></p><!-- markdownlint-enable no-inline-html --><p>…but I hope to continue exploring this Graphviz use case to further the application of structured data visualization to product management tooling.</p>]]></content>
    
    
    <summary type="html">I took a collaborative product planning technique and of course had to formalize it with code...</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="Product Management" scheme="https://modethirteen.com/tags/Product-Management/"/>
    
  </entry>
  
  <entry>
    <title>Cryptography 101</title>
    <link href="https://modethirteen.com/2019/06/07/cryptography-101/"/>
    <id>https://modethirteen.com/2019/06/07/cryptography-101/</id>
    <published>2019-06-08T03:44:19.000Z</published>
    <updated>2020-12-04T05:21:53.540Z</updated>
    
    <content type="html"><![CDATA[<p>At the end of every two-week <a href="https://mindtouch.com/">MindTouch Engineering</a> sprint, <a href="https://www.linkedin.com/in/pramert">Patty Ramert</a> hosts <em>Last Sprint Today</em>: a chance for us to share with other MindTouchers what we’ve learned, anything we are working on, or any technical topic of interest. This week, I presented an introduction to basic Cryptography, with some specific emphasis put on authorization and authentication flows.</p><div class="video-container"><iframe src="https://player.vimeo.com/video/484539617" frameborder="0" loading="lazy" allowfullscreen></iframe></div>]]></content>
    
    
    <summary type="html">The basics of hashing, signing, and encrypting data...</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="IAM" scheme="https://modethirteen.com/tags/IAM/"/>
    
    <category term="Last Sprint Today" scheme="https://modethirteen.com/tags/Last-Sprint-Today/"/>
    
  </entry>
  
  <entry>
    <title>What Can Apache Events and Workers Do for You?</title>
    <link href="https://modethirteen.com/2019/03/10/what-can-apache-events-and-workers-do-for-you/"/>
    <id>https://modethirteen.com/2019/03/10/what-can-apache-events-and-workers-do-for-you/</id>
    <published>2019-03-10T22:23:30.000Z</published>
    <updated>2020-11-27T23:06:27.000Z</updated>
    
    <content type="html"><![CDATA[<!-- markdownlint-disable no-inline-html --><p><img src="/2019/03/10/what-can-apache-events-and-workers-do-for-you/0_XFC0-JClTkkgF3CO.jpg" alt="Image"><span class="caption">Image: <a href="https://unsplash.com/@ianjbattaglia">Ian Battaglia</a></span></p><!-- markdownlint-enable no-inline-html --><p>There are <em>too many to count</em> articles about configuring Apache or NGINX to optimize for multi-process, multi-threaded scenarios with one or more downstream, detached processors of HTTP requests (<a href="https://www.php.net/manual/en/install.fpm.php">PHP-FPM</a>, <a href="https://www.phusionpassenger.com/">Passenger</a>, <a href="https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-apache">ASP.NET</a>, etc.). This post won’t be addressing the step-by-step configuration details. The key takeaway from most articles, that’s relevant here, is that it is often very taxing on high-load, performance-critical web servers when all possible request handlers and processors are engaged to satisfy every incoming client request - regardless of context. For example, a web server process doesn’t <em>need</em> <code>ruby</code> to load a static JavaScript file off disk.</p><!-- markdownlint-disable no-space-in-emphasis --><p>Due to <a href="https://mindtouch.com/">MindTouch</a>‘s <a href="/2014/11/10/continuous-delivery-without-breaking-everything/" title="evolution">evolution</a> from delivering downloadable packages for on-premise installs to handling deployment ourselves in a SaaS model, decisions regarding <em>how</em> a web server should be configured to best run our platform were not made by us in the early days. We adopted what seemed to be the most common way to run PHP (our middleware web layout application) with Apache: the <a href="https://httpd.apache.org/docs/2.4/mod/prefork.html">Apache Prefork MPM</a> (Multi-Processing Model) with the <code>mod_php</code> module. <em>Preforking</em> is quite nice and straightforward for serving HTML and static resources such as JavaScript, CSS, and images. With the addition of <code>mod_php</code>, every web server process that is forked from the main process has all the tools it needs to handle any supported incoming requests.</p><!-- markdownlint-enable no-space-in-emphasis --><p>MindTouch also had a unique requirement: a <code>mono</code> (.NET) hosted API host with its own rules for handling incoming requests. We used <code>mod_proxy</code> to direct traffic for a specific path segment to that downstream request handler.</p><p><img src="/2019/03/10/what-can-apache-events-and-workers-do-for-you/0.png" alt="Image"></p><p>…and this is how things probably would have remained if it wasn’t for <em>this</em> eventual problem:</p><p><img src="/2019/03/10/what-can-apache-events-and-workers-do-for-you/2.png" alt="Image"></p><p><em>Update 2020-11-27</em>: It is worth mentioning that MindTouch now deploys these components as containers on <a href="https://aws.amazon.com/eks">Amazon EKS</a> (k8s). When this post was written, all components described ran on EC2 application servers (with every application server configured with the same components).</p><p>Yes, on an average day, while our core API processes required less than 40% of available resources, <code>httpd</code> (with its bulky PHP add-on) spiked and was often CPU-bound. We analyzed traffic and found that we were loading the entire PHP interpreter to handle PHP-unnecessary requests such as images, JavaScript, and the API. We quickly realized we were not being particularly efficient with our compute resources. Much of our asset delivery was already handled through a content delivery network (CDN) as well, so we weren’t even getting the worst of it.</p><p>We chose to implement worker threads with the <a href="https://httpd.apache.org/docs/2.4/mod/event.html">Event MPM</a> for a leaner web server process. <del>Our longer-term plans include an initiative to deploy our web server, PHP middleware, and API host on separately scalable units (likely <a href="https://www.docker.com/resources/what-container">containers</a>), so breaking apart these components seemed like a step in achieving that goal as well.</del> (<em>Update 2020-11-27</em>: Done!) MindTouch’s hands-on VP of Technology, <a href="https://www.linkedin.com/in/pete-erickson-b3455a1">Pete Erickson</a>, was very instrumental in allowing me to execute these changes safely.</p><p><img src="/2019/03/10/what-can-apache-events-and-workers-do-for-you/1.png" alt="Image"></p><p>For a quick course in how threads operate in this context, I’ll do my best with the next few sentences (using the diagram above as a visual aid). The main web server process spawns child processes each with available worker threads and a single listener thread. The worker threads can be assigned to any incoming request received by the web server, and are expected to route the request to the appropriate downstream handler (the file system if fetching a static file, Fast CGI if another interpreter is necessary, etc.). This is already a much more efficient way to handle high rates of web traffic for different downstream destinations. However, the use of <em>events</em> and the listener thread makes this deployment fire on all cylinders.</p><p>Typically the worker thread would be <em>bound</em> to the web server socket, waiting for the downstream work to complete before returning some sort of response to the upstream client who originally sent the request. A CPU thread doesn’t <em>need</em> to sit around and do nothing while an operating system is trying to locate a file on disk, PHP is processing the received data, or the API is performing work. The listener thread <em>listens</em> for events fired from the main web server process socket queue of incoming requests and the operating system. It works with the process’s thread pool to determine when a worker thread needs to be called up to handle inbound or outbound communication for the webserver. If a request is presently being handled by a different process, there is no need for a web server thread to be tied up, and it can be available to handle incoming requests.</p><p>Incidentally, for you JavaScript enthusiasts, if this sounds a bit like concurrency as provided by the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop">JavaScript Event Loop</a>, it’s not 😛! While some similar benefits are realized in <a href="https://nodejs.org/">Node.js</a>, such as non-blocking I/O, JavaScript achieves this by managing a <em>single</em> thread very well. In the example above, we are talking about a <em>multi-threaded</em> solution, which is a good use case to apply <em>parallel computing</em>.</p><p>After tuning the number of child processes and threads, the outcome, with the same steady request rate, were significant:</p><p><img src="/2019/03/10/what-can-apache-events-and-workers-do-for-you/3.png" alt="Image"></p><p>We traded a collection of nearly CPU-bound Apache processes, for 15-20% utilization by PHP-FPM (the visualized “cliff” for the <code>httpd</code> processes represents when this change was fully rolled out to production servers). Getting the configuration right required a lot of testing and a lot of dead “canaries”, but the breathing room that we gained back led to a significantly more stable service for our customers (with more room for the occasional spike) and a much happier DevOps team!</p>]]></content>
    
    
    <summary type="html">You can have your CPU and RAM back...</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="Performance" scheme="https://modethirteen.com/tags/Performance/"/>
    
    <category term="DevOps" scheme="https://modethirteen.com/tags/DevOps/"/>
    
  </entry>
  
  <entry>
    <title>Demystifying Authorization and Authentication Flows</title>
    <link href="https://modethirteen.com/2019/01/25/demystifying-authorization-and-authentication-flows/"/>
    <id>https://modethirteen.com/2019/01/25/demystifying-authorization-and-authentication-flows/</id>
    <published>2019-01-26T03:21:05.000Z</published>
    <updated>2019-08-01T15:35:56.000Z</updated>
    
    <content type="html"><![CDATA[<p><em>Updated 2019-08-01</em>: In this video, I positioned the OAuth and OpenID Connect Implicit Flow as an implementation strategy for single-page web applications. Going forward, the new <a href="https://oauth.net/2/pkce">PKCE flow</a> should be used for any new implementations involving public clients such as mobile applications and SPAs. Check out <a href="https://developer.okta.com/blog/2019/05/01/is-the-oauth-implicit-flow-dead">this Okta developer blog article</a> for more details.</p><p>At the end of every two-week <a href="https://mindtouch.com/">MindTouch Engineering</a> sprint, <a href="https://www.linkedin.com/in/pramert">Patty Ramert</a> hosts <em>Last Sprint Today</em>: a chance for us to share with other MindTouchers what we’ve learned, anything we are working on, or any technical topic of interest. This week, I presented an introduction into the differences between three common authorization and authentication standards for Single Sign-On.</p><div class="video-container"><iframe src="https://player.vimeo.com/video/484320656" frameborder="0" loading="lazy" allowfullscreen></iframe></div><p>Slides:</p><!-- markdownlint-disable no-bare-urls --><iframe src="https://www.slideshare.net/slideshow/embed_code/key/th84EcSMPJJcx2" width="595" height="485" frameborder="0" loading="lazy" allowfullscreen></iframe><!-- markdownlint-enable no-bare-urls -->]]></content>
    
    
    <summary type="html">OAuth, OpenID Connect, and SAML: A basic primer...</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="IAM" scheme="https://modethirteen.com/tags/IAM/"/>
    
    <category term="Last Sprint Today" scheme="https://modethirteen.com/tags/Last-Sprint-Today/"/>
    
  </entry>
  
  <entry>
    <title>Witness the C64 MSSIAH</title>
    <link href="https://modethirteen.com/2018/10/24/witness-the-c64-mssiah/"/>
    <id>https://modethirteen.com/2018/10/24/witness-the-c64-mssiah/</id>
    <published>2018-10-25T01:50:30.000Z</published>
    <updated>2019-09-03T05:05:26.000Z</updated>
    
    <content type="html"><![CDATA[<p>This is a vanity post, and a plug for one of the best pieces of hardware/software that I’ve ever purchased: the C64 <a href="https://www.mssiah.com/">MSSIAH cartridge</a>. If you had any interest in the <a href="https://en.wikipedia.org/wiki/Elektron_SidStation">Elektron SidStation</a>, or maybe noticed that Elektron spent the early 2000s acquiring every spare SID sound synthesis chip they could get their hands on, then check this out:</p><div class="video-container"><iframe src="https://www.youtube.com/embed/AfDuZ2vHkUQ" frameborder="0" loading="lazy" allowfullscreen></iframe></div><p>A few more videos like this, and I was hooked.</p><p><img src="/2018/10/24/witness-the-c64-mssiah/IMG_20181024_174036959.jpg" alt="Image"></p><p>Once it arrived and I tried it with my original C64 (on the right), I was determined to track down another (well maintained, recapped, and tested) later-model C64 with an 8-pin audio/video DIN connector rather than the 5-pin connector on mine. For context, the 5-pin DIN is compatible with the type of composite video you would use with a television set in the 1980s (to play <a href="https://en.wikipedia.org/wiki/Moon_Patrol">Moon Patrol</a>, obviously). This was fine for my needs, as I acquired the machine (and a connector between 5-pin DIN and NTSC composite RCA) to play games from my childhood and forget that I turn 40 next year. The 8-pin connector is available on a C64 that supports a signal nearly equivalent to S-Video, and is supported by the official <a href="https://www.computerhistory.org/collections/catalog/X1518.98">Commodore 1702 Video Monitor</a>. The differences between the different ports are explained in more detail on the <a href="https://www.c64-wiki.com/wiki/A/V_Jack">C64 wiki</a>.</p><p>The odyssey to find a working 1702 Video Monitor that didn’t need <em>a lot</em> of work was pretty long, but I found one in great internal shape and <em>mostly</em> okay external shape (no major dents, scrapes, and just missing the panel that hides the video adjustment knobs). Both the monitor and the second, compatible, C64 are on the left.</p><p><img src="/2018/10/24/witness-the-c64-mssiah/IMG_20181022_181238853.jpg" alt="Image"></p><p>And there is MSSIAH’s mono synthesizer, with the RCA cable (carrying the signal from the C64’s SID) into my DAW. MSSIAH is now a first-class citizen of my (small) home rig. I’ll post some results when I have the time to, you know, actually write music with all this stuff.</p><p><img src="/2018/10/24/witness-the-c64-mssiah/IMG_20190902_202636_314.jpg" alt="Image"><br><img src="/2018/10/24/witness-the-c64-mssiah/IMG_20190304_220034502.jpg" alt="Image"></p>]]></content>
    
    
    <summary type="html">It makes your SID vicious! (Editor&#39;s Note: Not funny, get out)</summary>
    
    
    
    
    <category term="Demoscene" scheme="https://modethirteen.com/tags/Demoscene/"/>
    
    <category term="Commodore" scheme="https://modethirteen.com/tags/Commodore/"/>
    
    <category term="Hardware" scheme="https://modethirteen.com/tags/Hardware/"/>
    
    <category term="Music" scheme="https://modethirteen.com/tags/Music/"/>
    
  </entry>
  
  <entry>
    <title>The State of the Amiga 500 in 2018</title>
    <link href="https://modethirteen.com/2018/10/18/the-state-of-the-amiga-500-in-2018/"/>
    <id>https://modethirteen.com/2018/10/18/the-state-of-the-amiga-500-in-2018/</id>
    <published>2018-10-18T21:46:30.000Z</published>
    <updated>2019-02-03T19:17:26.000Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/2018/10/18/the-state-of-the-amiga-500-in-2018/IMG_20180805_212408568.jpg" alt="Image"></p><p>It’s here! The machine I fell in love with as a teenager, though it was horribly misunderstood here in the United States. I found this clean, recapped Amiga 500 for the single purpose of installing a <a href="https://www.apollo-accelerators.com/">Vampire accelerator</a> and turning it into a <a href="https://en.wikipedia.org/wiki/OctaMED">music tracking and sampling beast</a>. If you aren’t familiar with the Vampire line of Amiga accelerators, they are based on the <em>Apollo Core</em>, which in turn is based on a CPU that is code-compatible with the Motorola 68k CPUs available in the 1990s, but roughly three to four times faster. The added benefits include jacked-up RAM and video capabilities as well (though it’s possible my tracking software won’t be able to take advantage of next-gen video). The <a href="https://en.wikipedia.org/wiki/Motorola_68000_series">M68k</a> holds a special place in my heart, independent of its position as the Amiga 500 CPU, as it was the architecture that I first deep-dove into during my university days.</p><!-- markdownlint-disable no-space-in-emphasis --><p>Presuming not all my Amiga software can take advantage of next-gen video and thus, the Vampire accelerator’s HDMI connector, one of the first problems out of the gate is that this is not an IBM PC, and VGA was not a cross-architecture standard between device manufacturers in the days of the Amiga 500. The Amiga 500 was designed with a proprietary RGB D-Sub connector to an Amiga video monitor. It took me long enough to find a <a href="/2018/10/24/witness-the-c64-mssiah/" title="C64 video monitor in working condition">C64 video monitor in working condition</a>, so I wasn’t <em>particularly</em> interested in tracking down a working one for the Amiga.</p><!-- markdownlint-enable no-space-in-emphasis --><p>Fortunately, many schematics exist for wiring the VGA pins to a DB23 connector. I used <a href="https://www.ikod.se/rgb-to-vga">this one</a>, though I couldn’t locate a DB23 connector. I ended up purchasing a DB25 and using a Dremel to slice off the extra two pins for a snug fit in the Amiga’s RGB port.</p><p>The pins were wired correctly, but then it was a matter of finding a VGA monitor that still supports 15 kHz analog RGB video signals. This is a video mode that is extinct from all modern computer monitors, and was already nearly extinct by the time flat panel (LCD) monitors became common. One option was to locate a CRT that supported it. The great irony is, from a price perspective, I found compatible CRTs to be <em>more expensive</em> than the compatible LCD that I chose, due to the recent high-demand for CRTs to recreate a retro-gaming experience (I’ve probably junked a comfortable retirement’s worth of CRTs in my lifetime).</p><p><img src="/2018/10/18/the-state-of-the-amiga-500-in-2018/IMG_20181018_121504100.jpg" alt="Image"></p><p><a href="http://15khz.wikidot.com/">This wiki</a> helped me choose the NEC Multisync LCD1970NX. A flat panel was important due to space considerations in the eventual home studio build-out plan.</p><p>A MIDI connector, so that I could remotely start and stop the Amiga’s tracker in sync with Logic Pro X (on a separate MacBook Pro), was far less of a challenge to locate as plenty of parallel port-compatible DB25 MIDI I/O connectors are available on eBay or in various Amiga enthusiasts’ online shops. The same goes for a DB15 to USB connector for a modern mouse (I’m not exactly in love with the Amiga “tank mouse”). Be warned though, I had a lot of trouble with a wireless mouse behaving as expected. I ended up using a USB wired mouse with laser tracking.</p><p><img src="/2018/10/18/the-state-of-the-amiga-500-in-2018/IMG_20190202_201324_559.jpg" alt="Image"></p><p><em>Update 2019-02-03</em>: The Vampire accelerator arrived! I plan to post a step-by-step of my experience installing it, a more “modern” version of AmigaOS, and wire up my tracker software to the rest of the studio.</p>]]></content>
    
    
    <summary type="html">(Hint: It&#39;s still the best home computer ever made)</summary>
    
    
    
    
    <category term="Demoscene" scheme="https://modethirteen.com/tags/Demoscene/"/>
    
    <category term="Commodore" scheme="https://modethirteen.com/tags/Commodore/"/>
    
    <category term="Hardware" scheme="https://modethirteen.com/tags/Hardware/"/>
    
    <category term="Music" scheme="https://modethirteen.com/tags/Music/"/>
    
  </entry>
  
  <entry>
    <title>The Demoscene</title>
    <link href="https://modethirteen.com/2018/07/28/the-demoscene/"/>
    <id>https://modethirteen.com/2018/07/28/the-demoscene/</id>
    <published>2018-07-29T02:21:05.000Z</published>
    <updated>2019-11-27T06:48:50.000Z</updated>
    
    <content type="html"><![CDATA[<p><em>Update 2020-11-26</em>: <a href="https://www.vice.com/en/article/j5wgp7/who-killed-the-american-demoscene-synchrony-demoparty">Interesting companion piece I found from Motherboard (Vice)</a> that touches on a point that I made about the lethargy of the North American Demoscene.</p><p>At the end of every two-week <a href="https://mindtouch.com/">MindTouch Engineering</a> sprint, <a href="https://www.linkedin.com/in/pramert">Patty Ramert</a> hosts <em>Last Sprint Today</em>: a chance for us to share with other MindTouchers what we’ve learned, anything we are working on, or any technical topic of interest. This week, I presented a primer on the <a href="https://en.wikipedia.org/wiki/Demoscene">Demoscene</a>, which may be better described as “hackers gone wild” 😂.</p><div class="video-container"><iframe src="https://player.vimeo.com/video/484325098" frameborder="0" loading="lazy" allowfullscreen></iframe></div><p>Slides:</p><!-- markdownlint-disable no-bare-urls --><iframe src="https://www.slideshare.net/slideshow/embed_code/key/9MMTtKbKtqzNmC" width="595" height="485" frameborder="0" loading="lazy" allowfullscreen></iframe><!-- markdownlint-enable no-bare-urls -->]]></content>
    
    
    <summary type="html">How does a digital subculture develop?</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="Last Sprint Today" scheme="https://modethirteen.com/tags/Last-Sprint-Today/"/>
    
    <category term="Culture" scheme="https://modethirteen.com/tags/Culture/"/>
    
    <category term="Demoscene" scheme="https://modethirteen.com/tags/Demoscene/"/>
    
  </entry>
  
  <entry>
    <title>Introduction to the Chrome DevTools Protocol</title>
    <link href="https://modethirteen.com/2018/04/07/introduction-to-the-chrome-devtools-protocol/"/>
    <id>https://modethirteen.com/2018/04/07/introduction-to-the-chrome-devtools-protocol/</id>
    <published>2018-04-08T05:19:17.000Z</published>
    <updated>2020-12-04T05:21:53.550Z</updated>
    
    <content type="html"><![CDATA[<p>At the end of every two-week <a href="https://mindtouch.com/">MindTouch Engineering</a> sprint, <a href="https://www.linkedin.com/in/pramert">Patty Ramert</a> hosts <em>Last Sprint Today</em>: a chance for us to share with other MindTouchers what we’ve learned, anything we are working on, or any technical topic of interest. This week, I presented an introduction to the <a href="https://chromedevtools.github.io/devtools-protocol">Chrome DevTools Protocol</a>.</p><div class="video-container"><iframe src="https://player.vimeo.com/video/484328827" frameborder="0" loading="lazy" allowfullscreen></iframe></div><p>Here is the simple <a href="https://developers.google.com/web/tools/puppeteer">Puppeteer</a> code that I used for the demo (checking CSS rule coverage on a webpage):</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// simple puppeteer CSS coverage tracking</span></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.launch(&#123; <span class="attr">devtools</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.newPage();  </span><br><span class="line">  <span class="keyword">await</span> page.coverage.startCSSCoverage()</span><br><span class="line">  <span class="keyword">await</span> page.goto(<span class="string">&#x27;https://mindtouch.com&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> coverage = <span class="keyword">await</span> page.coverage.stopCSSCoverage();</span><br><span class="line">  <span class="keyword">await</span> browser.close();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// output coverage report</span></span><br><span class="line">  <span class="keyword">let</span> totalBytes = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">let</span> usedBytes = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">const</span> deadRules = [];</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">const</span> entry <span class="keyword">of</span> coverage) &#123;</span><br><span class="line">    totalBytes += entry.text.length;</span><br><span class="line">    <span class="keyword">if</span>(!entry.ranges.length) &#123;</span><br><span class="line">      deadRules.push(entry.text);  </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">const</span> range <span class="keyword">of</span> entry.ranges) &#123;</span><br><span class="line">      usedBytes += range.end - range.start - <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`Total bytes: <span class="subst">$&#123;totalBytes&#125;</span>`</span>);</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`Used bytes: <span class="subst">$&#123;usedBytes&#125;</span>`</span>);</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`Dead rules: <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(deadRules)&#125;</span>`</span>);</span><br><span class="line">&#125;)();</span><br><span class="line"></span><br><span class="line"><span class="comment">// manual implementation of CSS coverage tracking</span></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.launch(&#123; <span class="attr">devtools</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.newPage();</span><br><span class="line">  <span class="keyword">const</span> client = <span class="keyword">await</span> page.target().createCDPSession();</span><br><span class="line">  <span class="keyword">await</span> client.send(<span class="string">&#x27;CSS.startRuleUsageTracking&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> page.goto(<span class="string">&#x27;https://mindtouch.com&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> client.on(<span class="string">&#x27;SomeDomain.someEvent&#x27;</span>, <span class="function">(<span class="params">data</span>) =&gt;</span> doSomething(data));</span><br><span class="line">  <span class="keyword">const</span> coverage = <span class="keyword">await</span> client.send(<span class="string">&#x27;CSS.stopRuleUsageTracking&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// output coverage report</span></span><br><span class="line">  <span class="comment">// (coverage data is likely in a different format than page.coverage)</span></span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Automate your code inspections, debugging, and testing!</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="Last Sprint Today" scheme="https://modethirteen.com/tags/Last-Sprint-Today/"/>
    
    <category term="JavaScript" scheme="https://modethirteen.com/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Misadventures in Bundling Modules</title>
    <link href="https://modethirteen.com/2017/10/16/misadventures-in-bundling-modules/"/>
    <id>https://modethirteen.com/2017/10/16/misadventures-in-bundling-modules/</id>
    <published>2017-10-17T03:39:18.000Z</published>
    <updated>2020-12-04T05:21:53.550Z</updated>
    
    <content type="html"><![CDATA[<!-- markdownlint-disable no-inline-html --><p><img src="/2017/10/16/misadventures-in-bundling-modules/0_51ak67whYuVXrn1G.jpg" alt="Image"><span class="caption">Image: <a href="https://unsplash.com/@cebbbinghaus">Christopher Robin Ebbinghaus</a></span></p><!-- markdownlint-enable no-inline-html --><p>If you are writing modular JavaScript source code using ES2015’s import keyword, you are likely transpiling your code so that it can execute in web browsers presently available to most users. As much as it is a pleasure to maintain your source code as modules, it’s been a bumpy road to reach a common implementation of modules in the JavaScript execution environments themselves, resulting in a rather large and confusing set of technologies. The problem described later requires a bit of knowledge about the relationship between <a href="https://github.com/umdjs/umd">UMD</a>, <a href="http://requirejs.org/docs/whyamd.html">AMD</a>, and <a href="http://requirejs.org/">Require.JS</a>. <a href="http://davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd">This post</a> by David Calhoun gives a great overview.</p><p><a href="https://success.mindtouch.com/Integrations/Touchpoints">An embeddable widget library</a>, that our team develops and maintains, bundles the source JavaScript to simplify delivery to developers and integrators who rely on the library to embed in their websites. We didn’t notice that we had bundled them using UMD (Universal Module Definition) syntax. This led to problems when the library was added web applications leveraging the AMD syntax, like those depending on Require.JS. Transpiling to UMD included <code>window.require</code> as part of our bundle, leading to conflicts with the DOM’s <code>window.require</code> defined by the AMD-powered web application.</p><p>We corrected our mistake by transpiling to a global format. A word of caution: be careful when transpiling module syntax as there are many options for the target output. If you don’t have control over the webpage in which your code will execute (such as the case of an embeddable widget library), it’s wise to consider plain-old globals.</p>]]></content>
    
    
    <summary type="html">Native ES2015 module browser support can&#39;t come soon enough.</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="JavaScript" scheme="https://modethirteen.com/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Walmart Lab&#39;s Women In Tech Hackathon</title>
    <link href="https://modethirteen.com/2017/08/15/walmart-labs-women-in-tech-hackathon/"/>
    <id>https://modethirteen.com/2017/08/15/walmart-labs-women-in-tech-hackathon/</id>
    <published>2017-08-15T18:27:38.000Z</published>
    <updated>2020-12-04T05:21:53.555Z</updated>
    
    <content type="html"><![CDATA[<p>I was honored to co-host the <a href="https://www.hackathon.com/event/walmart-labs-react-hackathon-35213462414">@WalmartLabs hackathon</a> this past weekend. @WalmartLabs is the e-commerce technology arm of the largest retailer in the United States. Being an unabashed supporter of organizations like <a href="https://en.wikipedia.org/wiki/FC_St._Pauli">FC St. Pauli</a>, it’s only natural that some may view my association here as questionable or perhaps even hypocritical. I likely would not have been <em>overly</em> enthusiastic about the arrangement if it wasn’t for the fact that it was executed by a strong group of women engineers within Walmart Labs. Don’t let achieving perfection of global economic equity be the enemy of great recruiting opportunities for a historically marginalized gender in the tech space 😉. No organization is perfect, but from what I’ve observed, and learned since the hackathon, @WalmartLabs actually executes on the promises of inclusion that so many companies in our space continue to profess.</p><!-- markdownlint-disable no-bare-urls --><div class="twitter-wrapper"><blockquote class="twitter-tweet"><a href="https://twitter.com/Walmarttech/status/897968893087563776"></a></blockquote></div><script async defer src="//platform.twitter.com/widgets.js" charset="utf-8"></script><!-- markdownlint-enable no-bare-urls --><p>Hackathons aren’t easy to pull off - and it’s important not to try to do too much. If the event is focused on promoting or leveraging specific tools (this particular event was promoting @WalmartLabs <a href="http://www.electrode.io/">Electrode</a> React/Node.js framework), familiarity and training with the tools <em>before</em> a competition is underway is a must-have. For those of you planning an event, consider a scripted challenge with a clear result or outcome to achieve, using the promoted technology. Future hackathons, with increasingly more breadth and positioned to developers that develop mastery of the chosen technology, can build a dedicated community and strong advocates for the tech in the industry.</p><p><img src="/2017/08/15/walmart-labs-women-in-tech-hackathon/IMG_20170812_103400.jpg" alt="Image"><br><img src="/2017/08/15/walmart-labs-women-in-tech-hackathon/IMG_20170812_103412.jpg" alt="Image"><br><img src="/2017/08/15/walmart-labs-women-in-tech-hackathon/IMG_20170812_103422.jpg" alt="Image"></p>]]></content>
    
    
    <summary type="html">An insightful day of innovation and collaboration...</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="Culture" scheme="https://modethirteen.com/tags/Culture/"/>
    
  </entry>
  
  <entry>
    <title>#KANMF</title>
    <link href="https://modethirteen.com/2016/08/31/kanmf/"/>
    <id>https://modethirteen.com/2016/08/31/kanmf/</id>
    <published>2016-08-31T22:41:05.000Z</published>
    <updated>2019-11-14T02:14:53.000Z</updated>
    
    <content type="html"><![CDATA[<p>“#KANMF” entered the <a href="https://mindtouch.com/">MindTouch</a> lexicon last year as an unofficial company motto (along with our “no douchebags” policy):</p><ul><li>Kick</li><li>Ass</li><li>Ninja</li><li>Mother</li><li>Fu…</li></ul><p>…you get the idea. MindTouch has operated very lean for quite some time, and has always tried to hire people that help the organization punch above its weight class: those that see the limits on money, time, and resources as a challenge to get the blood pumping. I’ve had the occasional run-in with former technical colleagues that moved on to more established firms (Apple, Google, etc), and the common (paraphrased) refrain is this: “the results may not yet be exposed to the widest audience, but I did the best work of my career when I had to perform like a code ninja - gaining mastery across <em>the entire</em> surface area of the platform”. Point is, if that’s what you are looking for, you <em>probably</em> aren’t going to find it in <a href="https://en.wikipedia.org/wiki/Big_Tech">big tech</a>.</p><p>Some of the sales staff ran with the ninja concept and long story short, now we have <a href="https://en.wikipedia.org/wiki/Bokken">swords</a> 🙄. As playful as it is, I do have somewhat mixed feelings about these weapons knocking about the office, without at least a demonstration of how dangerous they can be. I’ve trained with, and taught at, <a href="https://www.pmakarate.com/">Pacific Martial Arts</a>, for many years, with <a href="https://en.wikipedia.org/wiki/Shint%C5%8D_Mus%C5%8D-ry%C5%AB">Shindō Musō-ryū</a> as a significant focus during the last several (I’m very much still an amateur with the bokken):</p><div class="video-container"><iframe src="https://player.vimeo.com/video/180914511" frameborder="0" loading="lazy" allowfullscreen></iframe></div><p><em>Update 2019-11-13</em>: I had the opportunity to demonstrate Shindo recently, giving it the level of decorum that a martial art deserves:</p><div class="instagram-wrapper"><blockquote class="instagram-media" data-instgrm-captioned data-instgrm-version="7" style=" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px auto; padding:0; width:99.375%; width:-webkit-calc(100%- 2px); width:calc(100% - 2px);"><div style="padding:8px;"> <div style=" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;"> <div style=" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;"></div></div><p style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;"><a href="https://www.instagram.com/p/B4wX6q4HV58/" style=" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;" target="_blank"></a></p></div></blockquote></div><script async defer src="//platform.instagram.com/en_US/embeds.js"></script>]]></content>
    
    
    <summary type="html">Swords in the office, what could go wrong?</summary>
    
    
    
    
    <category term="Culture" scheme="https://modethirteen.com/tags/Culture/"/>
    
  </entry>
  
  <entry>
    <title>Tear It Down, Build It Back Up: Infrastructure as Code</title>
    <link href="https://modethirteen.com/2015/05/31/tear-it-down-build-it-back-up-infrastructure-as-code/"/>
    <id>https://modethirteen.com/2015/05/31/tear-it-down-build-it-back-up-infrastructure-as-code/</id>
    <published>2015-06-01T05:30:20.000Z</published>
    <updated>2020-12-04T05:21:53.551Z</updated>
    
    <content type="html"><![CDATA[<p>I recently gave a short talk on how we manage our AWS infrastructure as code at <a href="http://gluecon.com/2015">Gluecon 2015</a>. Enjoy! …and despite the results, we did try our best with the audio!</p><div class="video-container"><iframe src="https://player.vimeo.com/video/129394310" frameborder="0" loading="lazy" allowfullscreen></iframe></div><p>Slides:</p><!-- markdownlint-disable no-bare-urls --><iframe src="https://www.slideshare.net/slideshow/embed_code/key/2RJTVMIxr4XiWD" width="595" height="485" frameborder="0" loading="lazy" allowfullscreen></iframe><!-- markdownlint-enable no-bare-urls -->]]></content>
    
    
    <summary type="html">If you really, really want to ssh to update your production servers, be my guest. For the rest of us...</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="DevOps" scheme="https://modethirteen.com/tags/DevOps/"/>
    
    <category term="AWS" scheme="https://modethirteen.com/tags/AWS/"/>
    
  </entry>
  
  <entry>
    <title>Fluent 2015</title>
    <link href="https://modethirteen.com/2015/04/24/fluent-2015/"/>
    <id>https://modethirteen.com/2015/04/24/fluent-2015/</id>
    <published>2015-04-25T06:22:58.000Z</published>
    <updated>2019-03-01T18:51:40.000Z</updated>
    
    <content type="html"><![CDATA[<ul><li>With the assistance of the <a href="https://www.linuxfoundation.org/">Linux Foundation</a>, <a href="https://github.com/nodejs/iojs.org">io.js</a> and <a href="https://nodejs.org/">Node.js</a> will join a community-led and industry-backed consortium to lead the development of Node.js (essentially <em>healing</em> the fork)</li></ul><!-- markdownlint-disable no-inline-html --><p><img src="/2015/04/24/fluent-2015/CDI-imVW0AAheFz.jpg" alt="Image"><span class="caption">We can all get along: Standard-bearers for io.js, Node.js, the Linux Foundation, me (because I’m awesome), and key industry leaders who rely on this ecosystem</span></p><!-- markdownlint-enable no-inline-html --><ul><li><a href="https://reactjs.org/">React.js</a> seems to be a proper application of the functional programming aspects of JavaScript that I actually like</li><li>Be an ES2015 (ES6) JavaScript module-first shop: developing and testing individual components is how real, big-boy codebases scale (transpile to global ES5 to deliver to today’s browsers, while developing for the future)</li><li>PHP takes further steps towards a Java-like syntax with <a href="https://www.php.net/releases/7_0_0.php">PHP 7</a></li></ul><!-- markdownlint-disable no-inline-html --><p><img src="/2015/04/24/fluent-2015/CDP0OMCUUAAscHK.jpg" alt="Image"><span class="caption"><a href="https://twitter.com/rasmus">Rasmus Lerdorf</a> said that PHP for a thin data marshaling layer is good and bloated PHP apps are bad - completely agree!</span></p><!-- markdownlint-enable no-inline-html -->]]></content>
    
    
    <summary type="html">Quick takeaways from Fluent 2015...</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="PHP" scheme="https://modethirteen.com/tags/PHP/"/>
    
    <category term="JavaScript" scheme="https://modethirteen.com/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Continuous Delivery Without Breaking Everything</title>
    <link href="https://modethirteen.com/2014/11/10/continuous-delivery-without-breaking-everything/"/>
    <id>https://modethirteen.com/2014/11/10/continuous-delivery-without-breaking-everything/</id>
    <published>2014-11-10T18:16:15.000Z</published>
    <updated>2020-11-27T20:36:15.000Z</updated>
    
    <content type="html"><![CDATA[<!-- markdownlint-disable no-inline-html --><p><img src="/2014/11/10/continuous-delivery-without-breaking-everything/0_hIc0MCrGilAtZXcO.jpg" alt="Image"><span class="caption">Image: <a href="https://unsplash.com/@scienceinhd">Science in HD</a></span></p><!-- markdownlint-enable no-inline-html --><p>Let’s start with one key fact: Continuous Delivery, though expanded from Continuous Integration, is <em>not</em> simply automating your code <em>deployment</em> pipeline like you automated your build architecture. Continuous Delivery is a shared business process, a contract between different members of product delivery to move software through stages to someone who will benefit, <em>without blocking it</em> or at best <em>avoiding unreasonably long delays</em>.</p><blockquote><p>Automation applied to an inefficient operation will magnify the inefficiency.</p><footer><strong>Bill Gates</strong></footer></blockquote><p>Where were we ten years ago? At your organization, it probably looked something like this: long product release cycles leading to long development and testing cycles with lots and lots of value bundled up in massive code drops.</p><p><img src="/2014/11/10/continuous-delivery-without-breaking-everything/0.png" alt="Image"></p><p>…followed by many smaller point releases to fix all the things that broke when you finally received validation from the real world - not just from internal testing or beta users. I’m sure it was <em>really fun</em> going back over six months of code to find the one change that broke the entire release 🙃.</p><p>This was <a href="https://mindtouch.com/">MindTouch</a> prior to 2012. We delivered software packages, available for system administrators to download and patch or upgrade our software running in data centers. Software-as-a-Service (SaaS) delivery became a near-term goal for us, as supporting our customers indirectly through their IT departments was becoming unmaintainable. We wrote the software, so we knew how best to deploy and run it!</p><p>However, long six-month development cycles were unsuitable for SaaS delivery and also under-leveraged one of the key business-benefits of SaaS: you can deliver value to your customers immediately.</p><!-- markdownlint-disable no-space-in-emphasis --><p>Much of our software <a href="/2014/02/25/automocking-dependencies/" title="was not in an ideal state">was not in an ideal state</a> for the “high-speed, always merge to the main branch, the main branch is deployed to customers” style of software delivery. As a result, we approached SaaS development and delivery in a more <em>cautious</em>, though not entirely pure <em>continuous</em> model:</p><!-- markdownlint-enable no-space-in-emphasis --><p><img src="/2014/11/10/continuous-delivery-without-breaking-everything/3.png" alt="Image"><br><img src="/2014/11/10/continuous-delivery-without-breaking-everything/4.png" alt="Image"></p><p>This is our current <a href="https://github.com/">GitHub</a> workflow. Yes, I know: Feature branches are not <em>continuous</em>. However, this is a transitional state for a codebase, which was until very recently tested end-to-end within six-month cycles, while its stabilized to be continuously delivered <em>whenever it needs to be</em>. For the time being, we’ve chosen weekly releases as our target. The immediate benefit achieved is a much smaller delta between changes resulting in quicker defect fixes (or rollbacks if necessary).</p><!-- markdownlint-disable no-inline-html --><p><img src="/2014/11/10/continuous-delivery-without-breaking-everything/1.png" alt="Image"><span class="caption">Rollbacks require that there is a previous feature branch or tag that we can rollback to - seemingly the antithesis of a continuous delivery process</span></p><!-- markdownlint-enable no-inline-html --><p>Technically speaking, the long-term goal is to deliver value when it’s needed and not let the tools dictate when we <em>can deliver</em>.</p><!-- markdownlint-disable no-inline-html --><p><img src="/2014/11/10/continuous-delivery-without-breaking-everything/2.png" alt="Image"><span class="caption">A single branch that always flows into live production requires confidence that the process will be resilient as possible and can quickly deliver corrections to defects as well as new features or value</span></p><!-- markdownlint-enable no-inline-html --><p>Test your Continuous Delivery process with humans first and make sure it works for how your organization needs to reliably deliver software today.</p>]]></content>
    
    
    <summary type="html">What is CD and, especially, what is it not?</summary>
    
    
    
    
    <category term="DevOps" scheme="https://modethirteen.com/tags/DevOps/"/>
    
  </entry>
  
  <entry>
    <title>Automocking Dependencies</title>
    <link href="https://modethirteen.com/2014/02/25/automocking-dependencies/"/>
    <id>https://modethirteen.com/2014/02/25/automocking-dependencies/</id>
    <published>2014-02-25T18:57:45.000Z</published>
    <updated>2020-11-26T17:36:18.000Z</updated>
    
    <content type="html"><![CDATA[<!-- markdownlint-disable no-inline-html --><p><img src="/2014/02/25/automocking-dependencies/photo-1546900703-cf06143d1239.jpg" alt="Image"><span class="caption">Image: <a href="https://unsplash.com/@goshua13">Joshua Aragon</a></span></p><!-- markdownlint-enable no-inline-html --><p><em>Update 2020-11-26</em>: I’ve updated this post to reflect the new <a href="https://github.com/modethirteen/OpenContainer">OpenContainer repository location and PHP 7.4 syntax</a>.</p><p>I created <a href="https://github.com/modethirteen/OpenContainer">OpenContainer</a> to assist in the refactoring of legacy PHP code at <a href="https://mindtouch.com/">MindTouch</a>. The goal was to shore up the stability of the codebase as we transitioned from six-month waterfall-driven software delivery cycles to continuous delivery twice per week. While the majority of critical business logic was implemented and exposed via APIs in a C# codebase with (fairly) okay test coverage, PHP was used to marshall data from these APIs and deliver a product experience. There were two major challenges to achieving reasonable stability and reliability for the PHP codebase:</p><ul><li>The codebase lacked any automated unit or integration testing</li><li>Nearly all code relied on static functions or global state, with no formalization in code of the relationship between dependencies</li></ul><p>The latter situation, in particular, got under my skin. I’ve never particularly been a fan of dynamic or loosely typed languages (it bothers me that we are so quick to throw decades worth of type system research out the window for so-called flexibility). Just about every global variable that could be mutated from anywhere across the codebase was mutated, usually as side effects of seemingly unrelated routines. If code paths were going to be tested, I first needed to understand what these paths were and to do that I had to define what was needed (depended upon) for any given scenario.</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">XyzzyService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">static</span> <span class="function"><span class="keyword">function</span> <span class="title">newXyzzy</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">global</span> <span class="variable">$wgXyzzySettings</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Xyzzy(<span class="variable">$wgXyzzySettings</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Foo</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">static</span> <span class="function"><span class="keyword">function</span> <span class="title">getXyzzy</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">global</span> <span class="variable">$wgSomeUnrelatedApplicationState</span>, <span class="variable">$wgXyzzySettings</span>;</span><br><span class="line">        <span class="variable">$wgSomeUnrelatedApplicationState</span> = <span class="string">&#x27;plugh&#x27;</span>;</span><br><span class="line">        <span class="variable">$wgXyzzySettings</span> = <span class="keyword">array</span>(</span><br><span class="line">            <span class="string">&#x27;baz&#x27;</span> =&gt; <span class="string">&#x27;qux&#x27;</span></span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">return</span> XyzzyService::getXyzzy();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Oh yea, there is absolutely nothing wrong with that scenario at all. <code>XyzzyFactory::newXyzzy</code> relies on a global variable that is set out-of-band in the function that calls it. <code>Foo::getXyzzy</code> cannot set different factory settings for testing and mutates a seemingly unrelated application state variable and ruins some other downstream component’s day.</p><p>I’m a tremendous fan of dependency injection as a concept (particularly by object constructor). With dependency injection properly leveraged, not only do I understand what the bare minimum requirements are for a software component to work, but the dependencies themselves can be provided as interfaces, with their actual implementations provided by particular use cases.</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">XyzzyService</span> <span class="keyword">implements</span> <span class="title">IXyzzyService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> IXyzzySettings <span class="variable">$settings</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__constructor</span>(<span class="params">IContainer <span class="variable">$container</span></span>) </span>&#123;</span><br><span class="line">        <span class="variable">$settings</span> = <span class="variable">$container</span>-&gt;IXyzzySettings;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">newXyzzy</span>(<span class="params"></span>) : <span class="title">IXyzzy</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Xyzzy(<span class="keyword">$this</span>-&gt;settings);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Foo</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> IXyzzySettings <span class="variable">$settings</span>;</span><br><span class="line">    <span class="keyword">private</span> IXyzzyService <span class="variable">$service</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__constructor</span>(<span class="params">IContainer <span class="variable">$container</span></span>) </span>&#123;</span><br><span class="line">        <span class="variable">$settings</span> = <span class="variable">$container</span>-&gt;IXyzzySettings;</span><br><span class="line">        <span class="variable">$settings</span>-&gt;set(<span class="string">&#x27;baz&#x27;</span>, <span class="string">&#x27;qux&#x27;</span>);</span><br><span class="line">        <span class="variable">$service</span> = <span class="variable">$container</span>-&gt;IXyzzyService;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getXyzzy</span>(<span class="params"></span>) : <span class="title">IXyzzy</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;service-&gt;newXyzzy();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Here I have achieved two benefits. <code>Foo</code> and <code>XyzzyService</code> both clearly define their outside dependencies by fetching them from a shared container. No untraceable global variables are overwritten, and I can step through this code in a debugger. I’d likely have a problem with the <code>Foo::__construct</code> method changing the internal state of the container’s <code>IXyzzySettings</code> instance, but the point is: at least I can track that down and identify when components may be altering state in a way that creates unintended consequences. Furthermore, now that the dependencies for <code>Foo</code> and <code>XyzzyService</code> are provided as interfaces, I can implement whatever state I want for <code>IXyzzySettings</code>. When I test <code>Foo</code>, I can even substitute a mock or dummy object for <code>IXyzzyService</code>, which leads me into mocking this container - or more specifically, <em>automocking</em>.</p><p>Automocking is what greatly increased my ability to quickly write tests to cover the behavior that I was converting from static and global implementations. The process went something like this: manually test the desired “hot” paths (based on the original product spec, if it existed), update the code, manually test again, lock down the behavior with a unit test, rinse, and repeat. Automocking removed the need to individually create mocks for all the possible dependencies that could exist in the container. It can be very tedious to identify all injected dependencies in an object and set them up as mocks in the event that they <em>may</em> be needed for a particular test. Failure to do so would often lead to null reference exceptions in the object’s constructor when just trying to initialize it for testing.</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Qux</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__constructor</span>(<span class="params">IContainer <span class="variable">$container</span></span>) </span>&#123;</span><br><span class="line">        <span class="variable">$bar</span> = <span class="variable">$container</span>-&gt;IBar</span><br><span class="line">        <span class="variable">$value</span> = <span class="variable">$container</span>-&gt;IFoo-&gt;getValue();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>For this scenario, I only need to test <code>Qux</code> with different implementations of <code>IBar</code>, but this object can’t initialize because <code>IFoo</code> is not mocked and <code>getValue</code> is called on a null reference. That’s pretty annoying - I have to mock a dependency I don’t care about. If only someone <em>else</em> could do it! 😂</p><p>The following sections assume that you have checked out <a href="https://github.com/modethirteen/OpenContainer">OpenContainer</a> and have familiarized yourself with the library. In short, the key piece we will leverage here is the <code>@property</code> PHPDoc value that we use with OpenContainer to create type hint friendly container dependencies (if you have a PHP IDE with intellisense such as <a href="https://www.jetbrains.com/phpstorm">JetBrains PHPStorm</a>).</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> <span class="title">modethirteen</span>\<span class="title">OpenContainer</span>\<span class="title">IContainer</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * in order for us to automock these properties</span></span><br><span class="line"><span class="comment"> * fully-qualified class names must be included</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@property</span> \My\Application\IFoo $IFoo</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@property</span> \My\Application\IBar $IBar</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@property</span> \My\Application\IQux $IQux</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">IApplicationContainer</span> <span class="keyword">extends</span> <span class="title">IContainer</span> </span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This container interface contains type hints for all the possible dependencies that could be registered in this container. One drawback to this approach is that it takes some diligence to remember to add a <code>@property</code> to the interface every time a new dependency is registered in the container. Our new <code>MockContainer</code> will implement this interface and provide some behavior to automatically generate <a href="https://github.com/sebastianbergmann/phpunit">PHPUnit</a> mocks for these dependencies.</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> <span class="title">phpDocumentor</span>\<span class="title">Reflection</span>\<span class="title">DocBlockFactory</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PHPUnit</span>\<span class="title">Framework</span>\<span class="title">MockObject</span>\<span class="title">Matcher</span>\<span class="title">AnyInvokedCount</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PHPUnit</span>\<span class="title">Framework</span>\<span class="title">MockObject</span>\<span class="title">MockBuilder</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PHPUnit</span>\<span class="title">Framework</span>\<span class="title">MockObject</span>\<span class="title">MockObject</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PHPUnit</span>\<span class="title">Framework</span>\<span class="title">TestCase</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">ReflectionClass</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MockContainer</span> <span class="keyword">implements</span> <span class="title">IApplicationContainer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@var</span> object[]</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="variable">$instances</span> = [];</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@var</span> MockObject[]</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@structure</span> [type] =&gt; MockObject</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="variable">$mocks</span> = [];</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@var</span> object[]</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@structure</span> [type] =&gt; object</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="variable">$proxyInstances</span> = [];</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@var</span> TestCase</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="variable">$testCase</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@var</span> string[]</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@structure</span> [type] =&gt; &#x27;class&#x27;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="variable">$typesToMock</span> = [];</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> TestCase $testCase</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span>(<span class="params">TestCase <span class="variable">$testCase</span></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;testCase = <span class="variable">$testCase</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// we&#x27;ll lazily build mocks from interface PHPdoc @property values</span></span><br><span class="line">        <span class="variable">$factory</span> = DocBlockFactory::createInstance();</span><br><span class="line">        <span class="variable">$docBlock</span> = <span class="variable">$factory</span></span><br><span class="line">            -&gt;create(</span><br><span class="line">               (<span class="keyword">new</span> ReflectionClass(IApplicationContainer::class))</span><br><span class="line">                   -&gt;getDocComment()</span><br><span class="line">            );</span><br><span class="line">        <span class="keyword">if</span>(<span class="variable">$docBlock</span> === <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">foreach</span>(<span class="variable">$docBlock</span>-&gt;getTagsByName(<span class="string">&#x27;property&#x27;</span>) <span class="keyword">as</span> <span class="variable">$tag</span>) &#123;</span><br><span class="line">            [<span class="variable">$class</span>, <span class="variable">$type</span>] = explode(<span class="string">&#x27; &#x27;</span>, strval(<span class="variable">$tag</span>));</span><br><span class="line">            <span class="variable">$type</span> = ltrim(<span class="variable">$type</span>, <span class="string">&#x27;$&#x27;</span>);</span><br><span class="line">            <span class="keyword">$this</span>-&gt;typesToMock[<span class="variable">$type</span>] = <span class="variable">$class</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Always prefer registered instances to mocks</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string $id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> object</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__get</span>(<span class="params"><span class="keyword">string</span> <span class="variable">$id</span></span>) : <span class="title">object</span> </span>&#123;</span><br><span class="line">        <span class="variable">$instance</span> = <span class="keyword">$this</span>-&gt;instances[<span class="variable">$id</span>];</span><br><span class="line">        <span class="keyword">if</span>(<span class="variable">$instance</span> !== <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="variable">$instance</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="variable">$mock</span> = <span class="keyword">$this</span>-&gt;getMock(<span class="variable">$id</span>);</span><br><span class="line">        <span class="keyword">if</span>(<span class="variable">$mock</span> !== <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="variable">$mock</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> CannotLoadInvalidMockInstanceException(<span class="variable">$id</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">isRegistered</span>(<span class="params"><span class="keyword">string</span> <span class="variable">$id</span></span>) : <span class="title">bool</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Get a mock out of the container to setup expectations</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string $id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> MockObject|null</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getMock</span>(<span class="params"><span class="keyword">string</span> <span class="variable">$id</span></span>) : ?<span class="title">MockObject</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="keyword">$this</span>-&gt;mocks[<span class="variable">$id</span>])) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;mocks[<span class="variable">$id</span>];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="variable">$mock</span> = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="keyword">$this</span>-&gt;typesToMock[<span class="variable">$id</span>])) &#123;</span><br><span class="line">            <span class="variable">$class</span> = <span class="keyword">$this</span>-&gt;typesToMock[<span class="variable">$id</span>];</span><br><span class="line">            <span class="variable">$builder</span> = <span class="keyword">new</span> MockBuilder(<span class="keyword">$this</span>-&gt;testCase, <span class="variable">$class</span>);</span><br><span class="line">            <span class="variable">$mock</span> = <span class="variable">$builder</span></span><br><span class="line">                -&gt;setMethods(get_class_methods(<span class="variable">$class</span>))</span><br><span class="line">                -&gt;disableOriginalConstructor()</span><br><span class="line">                -&gt;getMock();</span><br><span class="line">            <span class="keyword">$this</span>-&gt;mocks[<span class="variable">$id</span>] = <span class="variable">$mock</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable">$mock</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Register an instance that provides concrete properties and functions</span></span><br><span class="line"><span class="comment">     * that can be overridden with mocked properties and functions</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string $id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> object $instance</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">proxyMock</span>(<span class="params"><span class="keyword">string</span> <span class="variable">$id</span>, <span class="keyword">object</span> <span class="variable">$instance</span></span>) : <span class="title">void</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">isset</span>(<span class="keyword">$this</span>-&gt;proxyInstances[<span class="variable">$id</span>])) &#123;</span><br><span class="line">            <span class="keyword">$this</span>-&gt;proxyInstances[<span class="variable">$id</span>] = <span class="variable">$instance</span>;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;proxyInstances[<span class="variable">$id</span>] = <span class="variable">$instance</span>;</span><br><span class="line">        <span class="variable">$mock</span> = <span class="keyword">$this</span>-&gt;getMock(<span class="variable">$id</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// proxy each mock method to instance method</span></span><br><span class="line">        <span class="keyword">foreach</span>(get_class_methods(<span class="variable">$instance</span>) <span class="keyword">as</span> <span class="variable">$method</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span>(<span class="variable">$method</span> === <span class="string">&#x27;__clone&#x27;</span> || <span class="variable">$method</span> === <span class="string">&#x27;__construct&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="variable">$mock</span>-&gt;expects(<span class="keyword">new</span> AnyInvokedCount())</span><br><span class="line">                -&gt;method(<span class="variable">$method</span>)</span><br><span class="line">                -&gt;willReturnCallback(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) <span class="title">use</span> (<span class="params"><span class="variable">$id</span>, <span class="variable">$method</span></span>) </span>&#123;</span><br><span class="line">                    <span class="keyword">return</span> call_user_func_array([</span><br><span class="line">                        <span class="keyword">$this</span>-&gt;proxyInstances[<span class="variable">$id</span>],</span><br><span class="line">                        <span class="variable">$method</span></span><br><span class="line">                    ], func_get_args());</span><br><span class="line">                &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Register a mock object</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string $id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> MockObject $mock</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">registerMock</span>(<span class="params"><span class="keyword">string</span> <span class="variable">$id</span>, MockObject <span class="variable">$mock</span></span>) : <span class="title">void</span> </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;mocks[<span class="variable">$id</span>] = <span class="variable">$mock</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Remove any registered instance or mock</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string $id</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">flushInstance</span>(<span class="params"><span class="keyword">string</span> <span class="variable">$id</span></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">        <span class="keyword">unset</span>(<span class="keyword">$this</span>-&gt;instances[<span class="variable">$id</span>]);</span><br><span class="line">        <span class="keyword">unset</span>(<span class="keyword">$this</span>-&gt;mocks[<span class="variable">$id</span>]);</span><br><span class="line">        <span class="keyword">unset</span>(<span class="keyword">$this</span>-&gt;proxyInstances[<span class="variable">$id</span>]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Register a callback that builds an instance</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string $id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> Closure $builder</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">registerBuilder</span>(<span class="params"><span class="keyword">string</span> <span class="variable">$id</span>, <span class="built_in">Closure</span> <span class="variable">$builder</span></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Register an instance</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string $id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> object $instance</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">registerInstance</span>(<span class="params"><span class="keyword">string</span> <span class="variable">$id</span>, <span class="keyword">object</span> <span class="variable">$instance</span></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;instances[<span class="variable">$id</span>] = <span class="variable">$instance</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Register a class type</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string $id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> string $class</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">registerType</span>(<span class="params"><span class="keyword">string</span> <span class="variable">$id</span>, <span class="keyword">string</span> <span class="variable">$class</span></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Our <code>MockContainer</code> uses <a href="https://github.com/phpDocumentor/phpDocumentor">phpDocumentor</a> and PHPUnit to parse our container interface <code>@property</code> values and auto-generate mocks. In our tests, we can now <code>MockContainer::getMock</code> any dependency we need to set expectations on. We can use <code>MockContainer::proxyMock</code> to provide a concrete instance, with optional mocked properties and functions - a sort of hybrid approach that is influenced by JavaScript testing practices like <a href="https://jasmine.github.io/2.0/introduction#section-Spies">spies</a>.</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> <span class="title">PHPUnit</span>\<span class="title">Framework</span>\<span class="title">TestCase</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FooTest</span> <span class="keyword">extends</span> <span class="title">TestCase</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> IApplicationContainer <span class="variable">$container</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">setUp</span>(<span class="params"></span>) : <span class="title">void</span> </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;container = <span class="keyword">new</span> MockContainer(<span class="keyword">$this</span>);</span><br><span class="line">        <span class="keyword">$this</span>-&gt;container-&gt;getMock(<span class="string">&#x27;IBar&#x27;</span>)</span><br><span class="line">            -&gt;expects(<span class="built_in">static</span>::any())</span><br><span class="line">            -&gt;method(<span class="string">&#x27;getSomething&#x27;</span>)</span><br><span class="line">            -&gt;will(<span class="built_in">static</span>::returnValue(<span class="string">&#x27;123&#x27;</span>));</span><br><span class="line">        <span class="keyword">$this</span>-&gt;container-&gt;proxyMock(<span class="string">&#x27;IQux&#x27;</span>, <span class="keyword">new</span> Qux());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@test</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">test</span>(<span class="params"></span>) : <span class="title">void</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// this test has a very specific value it needs IQux to return</span></span><br><span class="line">        <span class="keyword">$this</span>-&gt;container-&gt;getMock(<span class="string">&#x27;IQux&#x27;</span>)</span><br><span class="line">            -&gt;expects(<span class="built_in">static</span>::once())</span><br><span class="line">            -&gt;method(<span class="string">&#x27;getSomethingElse&#x27;</span>)</span><br><span class="line">            -&gt;will(<span class="built_in">static</span>::returnValue(<span class="string">&#x27;456&#x27;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Applying these patterns has transformed a codebase that was responsible for a production bug nearly every week, to arguably one of the most covered with tests and most reliable in the entire platform. That level of confidence has a huge benefit on developer morale, especially on those who may be new to the codebase and are concerned with introducing bugs in an unfamiliar environment. Reliable dependency management and testing won’t ever entirely eliminate bugs (your unique production situations and data will always see to that), but it can get you very close!</p>]]></content>
    
    
    <summary type="html">A way to speed up your test-driven PHP development through automation...</summary>
    
    
    
    
    <category term="Programming" scheme="https://modethirteen.com/tags/Programming/"/>
    
    <category term="Testing" scheme="https://modethirteen.com/tags/Testing/"/>
    
    <category term="PHP" scheme="https://modethirteen.com/tags/PHP/"/>
    
  </entry>
  
</feed>
