<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: RJ Zaworski</title>
    <description>The latest articles on Forem by RJ Zaworski (@rjz).</description>
    <link>https://forem.com/rjz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F323477%2F77241e68-d048-4522-921d-bd0b57af6943.jpeg</url>
      <title>Forem: RJ Zaworski</title>
      <link>https://forem.com/rjz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rjz"/>
    <language>en</language>
    <item>
      <title>Unpacking the Technical Blog Post</title>
      <dc:creator>RJ Zaworski</dc:creator>
      <pubDate>Thu, 10 Aug 2023 21:31:42 +0000</pubDate>
      <link>https://forem.com/rjz/unpacking-the-technical-blog-post-33ak</link>
      <guid>https://forem.com/rjz/unpacking-the-technical-blog-post-33ak</guid>
      <description>&lt;p&gt;Technical blog posts are a fantastic place to share knowledge and lessons learned. They're also a powerful tool for building visibility and (in the right setting) attracting customers. While they share DNA with other types of technical writing, the marketing opportunities surrounding technical posts make them a distinct form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have a goal
&lt;/h2&gt;

&lt;p&gt;Before writing anything, get clear on the goal. Some of the motivation behind a technical post may be altruistic (sharing knowledge, advancing a theory, challenging assumptions, or otherwise joining in a broader community conversation), but even altruism can bring positive attention to the author. There's likely &lt;em&gt;some&lt;/em&gt; goal in the mix, and implicitly or otherwise it likely comes down to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Selling something. A technical post may be a good opportunity to introduce the audience to products, services, mailing lists, or even open job listings.&lt;/li&gt;
&lt;li&gt; Building visibility. Even a "sales-less" blog can raise the profile of the individual or organization that published it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These goals will likely stretch beyond a single post. A campaign to attract new sign-ups for a web application may be supported by a series of posts that all aim at the same segment of prospective users.&lt;/p&gt;

&lt;p&gt;Whatever the goal, it will inform the full cycle of the technical blog post---from who it's written for, to what it's written about, to how it's published and promoted.&lt;/p&gt;

&lt;p&gt;For now the thing to keep in mind is that the post &lt;em&gt;does&lt;/em&gt; have a goal, that the goal likely fits into a broader campaign, and that the goal is central to the post's construction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identify an audience
&lt;/h2&gt;

&lt;p&gt;The audience for any given technical topic will be much smaller than the Internet-going population as a whole. But there are technical audiences, and then there are technical audiences. Depending on the post's goals, it may aim to reach beginners, experts, kindred spirits, ideological opposites, or some combination of all four.&lt;/p&gt;

&lt;p&gt;Different audiences come with conflicting goals. Writing aimed at beginners will alienate advanced readers, while technical minutiae presented without background or context will alienate almost everyone else. Two good questions for finding the middle ground are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Will the audience be generally familiar with foundational concepts? If the post references algorithms, standards, or practices in widespread use, it may be safe to leave them at a name and a link. If they're more obscure (at least for the audience at hand) they may benefit from more explanation in line.&lt;/li&gt;
&lt;li&gt; How much familiarity will the reader have with the specifics? A new technology (or less experienced audience) will require more explanation than a familiar one applied in a new way. The same goes for ideas and references that aren't yet part of industry canon.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Just as readers arrive with different levels of experience, they also come from different points of view. A post that challenges accepted dogma will need to offer more evidence than one built on existing beliefs.&lt;/p&gt;

&lt;p&gt;No post will resonate with very reader, and that's OK. There's no such thing as one-size-fits-all content, and no amount of writing and re-writing will change that. The game is to identify a target audience and make good use of their time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick a topic that matters
&lt;/h2&gt;

&lt;p&gt;With the goal established and the audience defined, the next step is to winnow down a long list of potential topics to the ones that really matter.&lt;/p&gt;

&lt;p&gt;This shouldn't be a spur-of-the-moment decision. Developers know that &lt;a href="https://rjzaworski.com/2016/06/the-best-code-youll-never-write"&gt;there's an art to not writing software&lt;/a&gt;, and the same is true of blogging. Ideas are cheap, and skipping over so-so ideas early means more time for developing the really good ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep a list of ideas
&lt;/h3&gt;

&lt;p&gt;The first step in prioritizing what to write about is to list out all the possibilities. Do it in a spreadsheet, text file, or the nearest note-taking app, but do it in writing---it's a list that will grow over time.&lt;/p&gt;

&lt;p&gt;Once it's filled out, this list will likely include ideas for posts that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Demonstrate a technique&lt;/li&gt;
&lt;li&gt; Announce new projects, products, or features&lt;/li&gt;
&lt;li&gt; Answer burning questions&lt;/li&gt;
&lt;li&gt; Challenge assumptions&lt;/li&gt;
&lt;li&gt; Share lessons&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This list is also a good place to store new ideas that turn up while writing. Each post gets to make exactly one point. If new ones pop up and they're worth following up on, they'll make great posts of their own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prioritize the idea list
&lt;/h3&gt;

&lt;p&gt;It's easier to generate ideas than to pare them back down. The real art is in ruling out possible posts to get down to the ones that will have a genuine impact.&lt;/p&gt;

&lt;p&gt;As a starting place, reflect on how each topic or idea might be received:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; What will the reader learn?&lt;/li&gt;
&lt;li&gt; How will the reader's perception of [tool|technology|etc] change?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answers aren't clear and compelling, move on. Time is precious, and a reader who isn't finding value won't stick around.&lt;/p&gt;

&lt;p&gt;A second filter to apply is novelty. It's fine to overlap with other writing on the Internet, but a post that doesn't offer a fresh perspective, new idea, or meaningful contribution beyond existing content is a post that doesn't need to be written.&lt;/p&gt;

&lt;p&gt;Finally, consider how receptive the audience will (or won't) be to a post's central theme. There's an audience for every position, and most technical content isn't overly bombastic or provocative, But when you're representing a broader organization or brand, the old adage that "any press is good press" is one to handle with care.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the post
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Find a voice
&lt;/h3&gt;

&lt;p&gt;Technical writing prizes clarity over creativity and drama. There's still room to adjust tone to better connect with an audience, however, and particularly in the less-formal setting of a blog.&lt;/p&gt;

&lt;p&gt;For instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  a conversational tone can help invite an unsure reader into a how-to article&lt;/li&gt;
&lt;li&gt;  a more assertive posture may create an air of authority for press releases or feature announcements&lt;/li&gt;
&lt;li&gt;  a challenging tone may help spark debate (and a more conciliatory tone may help disarm it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, assign the narrator's point of view. The easiest way to do this is through pronouns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Singular first-person pronouns (I, me) underscore accountability and may help an audience relate to a post-mortem or &lt;em&gt;mea culpa.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  First-plural pronouns (we, us) replace a singular author with a collective---useful for representing an organization's general opinions or sharing its work.&lt;/li&gt;
&lt;li&gt;  Avoiding pronouns keeps the narrator out of things and may reduce the emotion out of the pieces at large.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different voices may be appropriate in different situations. The key is to be consistent. If an existing style guide is available for the publication, blog, or organization in question, use that. If it isn't, a little time invested in a lightweight approximation will save time in the long run. Are pronouns acceptable? Passive voice? Adverbs?&lt;/p&gt;

&lt;p&gt;Answer those questions up front, and the details will follow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tell a story
&lt;/h3&gt;

&lt;p&gt;Our brains come &lt;a href="https://hbr.org/2014/10/why-your-brain-loves-good-storytelling"&gt;wired to appreciate a good story&lt;/a&gt;. A little emotion and shared humanity &lt;a href="https://www.edutopia.org/article/neuroscience-narrative-and-memory"&gt;improve learning and retention&lt;/a&gt; over an impartial, procedural format, and increase the odds that a technical blog post will actually be read.&lt;/p&gt;

&lt;p&gt;Translating technical content into narrative form follows a fairly straightforward formula. Instead of straightforward exposition, the narrative version will build around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  a hero (who is it?)&lt;/li&gt;
&lt;li&gt;  a quest (what is the hero out to do?)&lt;/li&gt;
&lt;li&gt;  obstacles (what must the hero must overcome?)&lt;/li&gt;
&lt;li&gt;  a climax (what is the final obstacle?)&lt;/li&gt;
&lt;li&gt;  a reward (has the hero grown? has the world changed?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While the hero could be an operations team (in a post-mortem), a product manager (product release), or a software developer (a bug hunt), it may also be the reader herself. Tutorials and how-to articles often build around this structure:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By reading this post (quest), you (hero) will encounter the challenges (obstacles) on the way to a solution (climax) that you can use to accomplish some task (reward).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whoever it features, the story should always be aligned to the post's target audience. "Middle-manager uses technology to slay recalcitrant corporate bureaucracy" may draw sympathy for a general audience, but it likely won't inspire readers drawn by a headline about a cutting-edge development framework.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write the hook
&lt;/h3&gt;

&lt;p&gt;A blog post's hook is simply a reason to keep reading. In one sentence (two at the most) it sets the stage and makes a promise about what's yet to come. It should answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; What's the story? This may be explicit (as in a news &lt;a href="https://www.merriam-webster.com/dictionary/lede"&gt;lede&lt;/a&gt;), implicit (a destination revealed with little detail about the journey) or somewhere in between. A good hook will tease the story without giving everything away, piquing a reader's curiosity and drawing them deeper into the post.&lt;/li&gt;
&lt;li&gt; What's the benefit? Make the post's "value" concrete by telling the reader what (beyond a good story) they will gain by reading on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hook comes first. Write it last. Better yet, find some collaborators to toss ideas around and write several versions together. Settle on the ones that feel most compelling. After the article's headline and marketing metadata, the hook is the next most important step for drawing a reader in. It's worth the time it takes to get it right.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content: words, sentences, and examples
&lt;/h3&gt;

&lt;p&gt;"Content is king." What was true to Bill Gates in the mid-90s is no less true today, and no amount of marketing will make a lousy post worth reading. It starts with a clear goal (check), a great idea (yep), and a (hopefully) receptive audience.&lt;/p&gt;

&lt;p&gt;With technical content, the usual rules of good writing still apply. But the challenge of clear, economical writing is compounded by the challenge of creating clear, economical supporting examples that appeal to a wide range of audiences and individual learning styles.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Diagrams and illustrations can help a reader orient themselves within a multi-step discussion or workflow. They're most useful when simple and clearly labeled: a diagram that attempts too much may be better off as a standalone chart or infographic.&lt;/li&gt;
&lt;li&gt; Code samples translate abstractions into concrete terms. Since they're easiest to understand when focused on a single idea, it's usually more helpful to construct a post's examples in isolation than to try explaining their place within more complicated (and likely unfamiliar) real-world systems.&lt;/li&gt;
&lt;li&gt; Data aggregations, analyses, and visualizations are much more helpful than raw datasets. Simpler tables, charts, and algorithms are easier to understand and more likely to be used.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All examples, regardless of format, should include brief captions and links to relevant external references (data sets, source code, and so on).&lt;/p&gt;

&lt;p&gt;Different posts will follow different conventions, but they should all treat the audience's time with the highest respect. The audience is filled with busy people who have other places to be. The central idea needs to be clear. Details need to clearly support it.&lt;/p&gt;

&lt;p&gt;Remember: a post gets exactly one call to action, and it belongs near the end. A reader will only see it if they have a reason to keep reading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrap it all up
&lt;/h3&gt;

&lt;p&gt;A post's conclusion is a chance to restate key points and revisit the hook's promise. Did the post deliver?&lt;/p&gt;

&lt;p&gt;Since it did, there is only one thing left to do. Make the ask. The post had a goal. Whether it was to sell something, build visibility, or just contribute to the broader body of knowledge, it's time to request something from the reader in return. That's the call to action (CTA), and there should only be one.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Hire me (us)&lt;/li&gt;
&lt;li&gt; Buy my (our) product or services&lt;/li&gt;
&lt;li&gt; Follow me (us) and/or share this post on social media&lt;/li&gt;
&lt;li&gt; Review our job listing&lt;/li&gt;
&lt;li&gt; ...and so on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Make this clear, keep it brief, and put it in context of the rest of the post.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Like what you've read? Signing up for our newsletter is the best way to discover great new posts like this one.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;Want to stay in the loop? Follow us on social media for ongoing coverage.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;These are the sorts of problems we solve every day. Did we mention that we're hiring?&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this, the post has come full circle. After drawing an interested audience and giving them a unique insight, feature, or story, the CTA makes good on the goal that drove the post in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish and promote
&lt;/h2&gt;

&lt;p&gt;Professional marketers spend their days figuring out how to reach potential customers with the right message at the right time. It's a fuzzy problem with a vast &lt;a href="https://en.wikipedia.org/wiki/Search_space"&gt;search space&lt;/a&gt; and very few clear answers, but a technical blog post can get pretty far by focusing in on three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The channels that draw readers to the post&lt;/li&gt;
&lt;li&gt; The conversion from the post's CTA&lt;/li&gt;
&lt;li&gt; The analytics that connect channels to conversions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In other words: how do people find the post? is the post achieving its goal? and how do we know?&lt;/p&gt;

&lt;h3&gt;
  
  
  Marketing channels
&lt;/h3&gt;

&lt;p&gt;Unless a post has the benefit of an established audience (via a newsletter, for instance) or a non-trivial marketing budget, readers will likely find it through one of three channels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; "Owned" media - via social media (Twitter, LinkedIn, etc) or forum (&lt;a href="http://news.ycombinator.com/"&gt;Hacker news&lt;/a&gt;, reddit) posts from accounts managed by the post's author. This channel depends on interesting headlines and compelling post previews.&lt;/li&gt;
&lt;li&gt; "Earned" media - via shares or syndication from other social accounts&lt;/li&gt;
&lt;li&gt; Search engines - via longer-run Search Engine Optimization (SEO) campaigns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Channel strategies may overlap, but don't count on it. Search engine rankings take time and careful keyword planning; social posts may disregard keywords entirely while presenting enticing, clickable headlines headline. "Evergreen" content will rarely be able to ride a wave of current events, but more topical content may quickly lose relevance.&lt;/p&gt;

&lt;p&gt;The key is to pick a single channel and put the time in to develop it. While every channel has its strengths and weaknesses, steady ongoing efforts to attract followers (owned media); build relationships (earned media); and build credibility (search engines) will help them reach their full potential. Whatever the focus, one reliable hit will always be better than three or four near misses.&lt;/p&gt;

&lt;p&gt;Finally, though additional channels (e.g. emails/newsletters, paid advertising, syndication, etc) may help boost reach in a more mature marketing campaign, they will only add---not replace---the big three.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conversions
&lt;/h3&gt;

&lt;p&gt;For the purpose of the post's goal, a conversion is a user answering the CTA.&lt;/p&gt;

&lt;p&gt;For the purpose of tuning the post towards that goal, it's helpful to think of the post as its own little funnel. Multiple "conversions" lead up to the CTA, as a reader:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  finds the post&lt;/li&gt;
&lt;li&gt;  finishes reading it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A post that readers aren't finding, or a channel that's underperforming, implies low visibility, a lack of interest, or both. It may be possible to tweak the post's preview (title, description, and opengraph/structured data) to improve discoverability. It may also just be a miss on audience, topic, or channel. These things happen. Time to take a different swing.&lt;/p&gt;

&lt;p&gt;If readers aren't reaching the entire post, it may be down to a perceived lack of value---or to the promise being adequately fulfilled before the end. This is an easier problem than getting people in the door in the first place, but no less crippling to the blog post's goals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instrumentation and analytics
&lt;/h3&gt;

&lt;p&gt;Google analytics' defaults will go pretty far, especially if inbound links include sensible &lt;a href="https://support.google.com/analytics/answer/1033863?hl=en"&gt;&lt;code&gt;utm&lt;/code&gt; parameters&lt;/a&gt;. The channel breakout will show where visitors are arriving from; with an instrumented call to action (if it's a form, button, or anything other than a link) it's a small step to track conversions the whole way through the campaign.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That's it: from ideation to promotion, a comprehensive look at crafting interesting, insightful technical blog posts. And don't forget to share a link as soon as that post's finished!&lt;/p&gt;

</description>
      <category>writing</category>
      <category>blog</category>
      <category>learning</category>
    </item>
    <item>
      <title>Managing Application State with Algebraic Effects</title>
      <dc:creator>RJ Zaworski</dc:creator>
      <pubDate>Fri, 31 Dec 2021 19:41:31 +0000</pubDate>
      <link>https://forem.com/rjz/managing-application-state-with-algebraic-effects-13a7</link>
      <guid>https://forem.com/rjz/managing-application-state-with-algebraic-effects-13a7</guid>
      <description>&lt;p&gt;&lt;em&gt;"And how's that working out for you?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You get used to the awe, horror, skepticism, and (occasionally) curiosity whenever someone learns out that your &lt;a href="https://koan.co"&gt;startup&lt;/a&gt;'s built on top of &lt;a href="https://aws.amazon.com/dynamodb/"&gt;DynamoDB&lt;/a&gt;. I get it. Starting with Dynamo means you're either supremely confident in your business model, domain model, and the data-access patterns that result--or utterly unconcerned about paying-as-you-go for the batteries that weren't included. Schema design is agony. We sunk far too much development time into &lt;code&gt;JOIN&lt;/code&gt;-ing data and reconstructing other basic functionality. But as we learned to live with it we also got a very flat performance curve and a decent handle on what works and what doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live and learn
&lt;/h2&gt;

&lt;p&gt;If we went back and did it all again, two decisions made later in the game would have enormously improved our quality of life at the start.&lt;/p&gt;

&lt;p&gt;Decision one: ditching our ORM. DynamoDB is not a relational database, and any library that tries to nudge in that direction is a fast road to the wrong access patterns and a lousy developer experience. Life got much better when &lt;a href="https://twitter.com/swac"&gt;Ashwin Bhat&lt;/a&gt; started tearing up the edges of that inappropriate abstraction in favor of the &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html"&gt;DynamoDB &lt;code&gt;DocumentClient&lt;/code&gt;&lt;/a&gt; and a successful &lt;a href="https://medium.com/developing-koan/modeling-graph-relationships-in-dynamodb-c06141612a70"&gt;single-table design&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The other key change was to borrow heavily from &lt;a href="https://www.eff-lang.org/handlers-tutorial.pdf"&gt;effects-based programming&lt;/a&gt; for non-trivial state updates. At risk of ruining the punchline, most web apps are variations on the same four-part theme: load data, prepare changes, apply changes, repeat. Expressing the middle two stages in terms of "effects", even in a crude, homebuilt form, had a profound impact on our application as a whole, unlocking performance, improving visibility, and decreasing cycle times for new development.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example
&lt;/h2&gt;

&lt;p&gt;Before we get to the good stuff, consider a web service responsible for managing a team's roster. It probably includes a way to add a new user to a team, probably using a method like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;addUserToTeams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TeamId&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMembers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is neat and concise, but it comes with at least a few, &lt;em&gt;ahem&lt;/em&gt;, concerns.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The business logic around adding users who are already part of the team is poorly defined. Right now we simply skip the operation while other changes take place in parallel--we don't make any attempt to roll back, or notify the caller about a partial failure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Whatever the business logic ought to be, interspersing data access throughout the implementation will make it harder to test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Likewise, any exception-handling logic must either exist at the data layer (limiting coordination across parallel requests) or implemented as a one-off inside this method.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of these are solvable problems, however, and teasing out the &lt;code&gt;load-prepare-apply&lt;/code&gt; pipeline implicit in the naïve implementation is a good place to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changes as effects
&lt;/h2&gt;

&lt;p&gt;As a first step, let's separate computing the changes to make (the business logic) from how they're sent to the database. We'll then glue them together using a list of effects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MembershipEffect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TeamId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;MembershipEffect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADD_MEMBER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;MembershipEffect&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEL_MEMBER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// etc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, the business logic. Instead of writing directly to the datastore as we were before, we'll now produce an &lt;code&gt;'ADD_MEMBER'&lt;/code&gt; effect representing a user who needs to be added.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;prepareAddUserToTeams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TeamId&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getMembers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADD_MEMBER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we've addressed one of the shortcomings of the original implementation (separating out a failable DB write) while also gaining the ability to peek into the "performed" effects before some or all of them have been written. More on that in a moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Effect processing
&lt;/h2&gt;

&lt;p&gt;Now that we're producing effects we need a way to apply them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;applyMemberships&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADD_MEMBER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Not implemented: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;applyMemberships&lt;/code&gt; is a minimal effect processor, nothing more. It doesn't care where the events came from. It only cares that some upstream logic coughed them up, and--now that they're here--that they get applied to the application state. And since it's a pure, standalone function, it's easily extended. That could mean providing a general-purpose rollback strategy when event processing fails, or providing alternative "commit" strategies to ensure data is persisted via an appropriate API.&lt;/p&gt;

&lt;p&gt;With DynamoDB, for instance, a single call to &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html"&gt;&lt;code&gt;TransactWriteItems&lt;/code&gt;&lt;/a&gt; (if we needed transactional checks or guarantees) or &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html"&gt;&lt;code&gt;BatchWriteItem&lt;/code&gt;&lt;/a&gt; (the rest of the time) will be both safer and cheaper than adding team member in separate &lt;code&gt;PutItem&lt;/code&gt; requests. Making the switch is a small change to &lt;code&gt;applyMemberships&lt;/code&gt;: just batch up the memberships and write them simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;applyMemberships&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;putItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADD_MEMBER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asPutMembershipItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// in real life we would chunk large-n batches&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;docClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;batchWriteItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;putItems&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crucially, we can make this adjustment &lt;em&gt;without changing business logic&lt;/em&gt;! The same rules still apply. &lt;code&gt;applyMemberships&lt;/code&gt; only cares about how effects are interpreted. If we need to change how data are written, we just do it. If we need to trigger a welcome email or notify a billing service, we could add an additional effect--or, if inextricably linked to the &lt;code&gt;ADD_MEMBER&lt;/code&gt; effect, we can just tweak the processor to make sure they happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logic and context
&lt;/h2&gt;

&lt;p&gt;We can refactor &lt;code&gt;prepareAddUserToTeams&lt;/code&gt; just as freely. For instance, we might finish decoupling business logic and data access by preloading memberships (or any other relevant context).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;membersByTeamId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TeamId&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;loadMembershipContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TeamId&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;teamMembers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Teams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;batchGetMembers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;teamMembers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;membersByTeamId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With all of the I/O handled externally, the logic around adding new members condenses into an almost-trivial (and blissfully &lt;a href="https://en.wikipedia.org/wiki/Pure_function"&gt;pure&lt;/a&gt;!) function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;prepareAddUserToTeams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;membersByTeamId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADD_MEMBER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pipes all the way down
&lt;/h2&gt;

&lt;p&gt;The somewhat naive implementation we started with has gotten considerably more verbose. In return, decoupling the &lt;code&gt;load-prepare-apply&lt;/code&gt; stages has yielded reusable solutions to two-thirds of &lt;em&gt;any&lt;/em&gt; change related to the team's membership roster. Implement the business logic and you're off!&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;load&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; building blocks, we can now collapse &lt;code&gt;addUserToTeams&lt;/code&gt; down into something much more concise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;addUserToTeams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TeamId&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;loadMembershipContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prepareAddUserToTeams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;applyMemberships&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're familiar with Node's &lt;a href="https://nodejs.org/api/stream.html"&gt;Stream API&lt;/a&gt; (or pretty much any functional programming language) you'll recognize a pipeline in the making. Here's how it would look if the &lt;a href="https://docs.hhvm.com/hack/expressions-and-operators/pipe"&gt;Hack-style&lt;/a&gt; pipelines currently favored in the (still-very-unsettled) &lt;a href="https://github.com/tc39/proposal-pipeline-operator"&gt;TC39 Pipeline proposal&lt;/a&gt;) were adopted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;addUserToTeams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;loadMembershipContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prepareAddUserToTeams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;applyMemberships&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides increasing testability, reusability, and confidence in each step, we can now add additional business logic independent of the data layer. For example, the same building blocks easily recombine into a new API method for populating&lt;br&gt;
an entire team's roster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;populateTeam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TeamIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;loadMembershipContext&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;teamId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;effects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;prepareAddUserToTeams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;applyMemberships&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effects&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After updating &lt;code&gt;applyMemberships&lt;/code&gt; to handle &lt;code&gt;DEL_MEMBER&lt;/code&gt; and &lt;code&gt;SET_ACCESS&lt;/code&gt; effects (with some attention required around de-duplication and ordering), we could go on to implement an entire roster-management application with only minimal regard for where and how it's persisted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back in the real world
&lt;/h2&gt;

&lt;p&gt;An effect-based approach adds undeniable indirection and (local) complexity. For a method as simple as &lt;code&gt;addUserToTeams&lt;/code&gt;, our naive first implementation may be the right way to go. As users, side effects, or the surface area of our membership API increase, however, effects provide a fairly straightforward way to manage them. They might:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;provide preflight mechanisms for data migrations (by inspecting a migration's effects &lt;em&gt;before&lt;/em&gt; applying it)&lt;/li&gt;
&lt;li&gt;simplify development of batch operations (e.g.  archiving or deleting many related nodes in a graph) by encouraging proven, reusable load/apply methods&lt;/li&gt;
&lt;li&gt;simplify complex business processes (e.g. computing and synchronizing billing details owned by a mix of 1st- and 3rd-party systems) by isolating effects from application.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ultimately, using effects as the basis the &lt;code&gt;load-prepare-apply&lt;/code&gt; pipeline at the heart of most state changes isn't the big step it seems. Yes, it takes time to verify mostly-independent parts and reconstitute them into working whole. But once built, and once trusted, they tremendously accelerate future development.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Cover image by &lt;a href="https://unsplash.com/@iurte"&gt;Iker Urteaga&lt;/a&gt; via &lt;a href="https://unsplash.com/photos/TL5Vy1IM-uA"&gt;unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>programming</category>
      <category>database</category>
    </item>
    <item>
      <title>Anatomy of a high-velocity CI/CD pipeline</title>
      <dc:creator>RJ Zaworski</dc:creator>
      <pubDate>Thu, 02 Dec 2021 16:35:17 +0000</pubDate>
      <link>https://forem.com/koan/anatomy-of-a-high-velocity-cicd-pipeline-251o</link>
      <guid>https://forem.com/koan/anatomy-of-a-high-velocity-cicd-pipeline-251o</guid>
      <description>&lt;p&gt;If you’re going to optimize your development process for one thing, make it speed. Not the kind of speed that racks up technical debt on the team credit card or burns everyone out with breathless sprints, though. No, the kind of speed that treats time as your most precious resource, which it is.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Speed is the startup’s greatest advantage&lt;/em&gt;. Speed means not wasting time. Incorporating new information as soon as it’s available. Getting products to market. Learning from customers. And responding quickly when problems occur. But speed with no safeguards is simply recklessness. Moving fast requires systems for ensuring we’re still on the rails.&lt;/p&gt;

&lt;p&gt;We’ve woven many such systems into the sociotechnical fabric of our startup, but maybe the most crucial among them are the continuous integration and continuous delivery processes that keep our work moving swiftly towards production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The business case for CI/CD
&lt;/h2&gt;

&lt;p&gt;Writing in 2021 it’s hard to imagine building web applications without the benefits of continuous integration, continuous delivery, or both. Running an effective CI/CD pipeline won’t score points in a sales pitch or (most) investor decks, but it can make significant strategic contributions to both business outcomes and developer quality of life. The virtuous cycle goes something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  faster feedback&lt;/li&gt;
&lt;li&gt;  fewer bugs&lt;/li&gt;
&lt;li&gt;  increased confidence&lt;/li&gt;
&lt;li&gt;  faster releases&lt;/li&gt;
&lt;li&gt;  more feedback (even faster this time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even on teams (like ours!) that haven’t embraced the dogma (or overhead) of capital-A-Agile processes, having the confidence to release early and often still unlocks shorter development cycles reduces time to market.&lt;/p&gt;

&lt;p&gt;As a developer, you’re probably already bought into this idea. If you’re feeling resistance, though, here’s a quick summary for the boss:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sVyKVIDc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2AI0ZsHIQl7d5L0NmxozdnrA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sVyKVIDc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2AI0ZsHIQl7d5L0NmxozdnrA.png" alt="Graphic illustrating the business case for continuous integration and delivery: feedback, quality, confidence, velocity." width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The business case for continuous integration and delivery&lt;/p&gt;

&lt;h2&gt;
  
  
  Is CI/CD worth the effort?
&lt;/h2&gt;

&lt;p&gt;Nobody likes a red build status indicator, but the truth is that builds fail. That’s why status dashboards exist, and a dashboard glowing crimson in the light of failing builds is much, much better than no dashboard at all.&lt;/p&gt;

&lt;p&gt;Still, that dashboard (nevermind the systems and subsystems it’s reporting on) is pure overhead. Not only are you on the hook to maintain code and release a dozen new features by the end of the week, but also the litany of scripts, tests, configuration files, and dashboards needed to build, verify, and deploy it. When the server farm of Mac Minis in the basement hangs, you’re on the hook to restart it. That’s less time available to actually build the app.&lt;/p&gt;

&lt;p&gt;This is a false dilemma, though. You can solve this problem by throwing resources at it. Managed services eliminate much of the maintenance burden, and when you’ve reached the scale where one-size-fits-all managed services break down you can likely afford to pay a full-time employee to manage Jenkins.&lt;/p&gt;

&lt;p&gt;So, there are excuses for not having a reliable CI/CD pipeline. They just aren’t very good ones. The payoff — in confidence, quality, velocity, learning, or &lt;em&gt;whatever&lt;/em&gt; you hope to get out of shipping more software — is well worth any pain the pipeline incurs.&lt;/p&gt;

&lt;p&gt;Yes, even if it has to pass through XCode.&lt;/p&gt;

&lt;h2&gt;
  
  
  A guiding principle
&lt;/h2&gt;

&lt;p&gt;Rather than prescribing the ultimate CI/CD pipeline in an edict from on-high, we’ve taken guidance from one of our team principles and evolved our practices and automation from there. It reads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ship to Learn&lt;/strong&gt;. We release the moment that staging is better than prod, listen early and often, and move faster because of it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Continuous integration is a big part of the story, of course, but the same guidance applies back to the pipeline itself.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Releasing the moment that staging is better than prod&lt;/strong&gt; is easy to do. This is nearly always the case, and keeping up with it means having both a lightweight release process and confidence in our work. Individual investment and a reasonably robust test suite are all well and good; better is having a CI/CD pipeline that makes them the norm (if not the rule).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Listening early and often&lt;/strong&gt; is all about gathering feedback as quickly as we possibly can. The sooner we understand whether something is working or not, the faster we can know whether to double down or adapt. Feedback in seconds is better than in minutes (and certainly better than hours).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Moving faster&lt;/strong&gt; includes product velocity, of course, but also the CI/CD process itself. Over time we’ve automated what we reasonably can; still, several exception-heavy stages remain in human hands. We don’t expect to change these soon, so here “moving fast” means enabling manual review and acceptance testing, but we don’t expect to replace them entirely any time soon.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  So, our pipeline
&lt;/h2&gt;

&lt;p&gt;Product velocity depends on the pipeline that enables it. With that in mind, we’ve constructed our pipeline to address the hypothesis that &lt;em&gt;issues uncovered at any stage are more exponentially expensive to fix than those solved at prior stages&lt;/em&gt;. Issues &lt;em&gt;will&lt;/em&gt; happen, but checks that uncover them early on drastically reduce friction at the later, more extensive stages of the pipeline.&lt;/p&gt;

&lt;p&gt;Here’s the boss-friendly version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N5w-tW7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2AsxIjoTACEZbnafUfLvwgKA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N5w-tW7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2AsxIjoTACEZbnafUfLvwgKA.png" alt="A CI/CD pipeline and the time required to test at each stage" width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test early, test often&lt;/p&gt;

&lt;h2&gt;
  
  
  Local development
&lt;/h2&gt;

&lt;p&gt;Continuous integration starts immediately. If you disagree, consider the feedback time needed to integrate and test locally versus anywhere else. It’s seconds (rebasing against our &lt;code&gt;main&lt;/code&gt; branch or acting on feedback from a pair-programming partner), minutes (a full run of our test suite) or less.&lt;/p&gt;

&lt;p&gt;We’ve made much of if automatic. Our editors are configured to take care of &lt;a href="https://prettier.io/"&gt;styles and formatting&lt;/a&gt;; &lt;a href="https://dev.to/developing-koan/porting-koans-150-000-line-javascript-codebase-to-typescript-b4818ccc42ac"&gt;TypeScript provides a first layer of testing&lt;/a&gt;; and &lt;a href="https://rjzaworski.com/2018/01/keeping-git-hooks-in-sync"&gt;shared git hooks&lt;/a&gt; run project-specific static checks.&lt;/p&gt;

&lt;p&gt;One check we don’t enforce is to run our full test suite. Run time goes up linearly with the size of a test suite, and — while we’re culturally averse to writing tests for their own sake — running our entire suite on every commit would be prohibitively expensive. &lt;em&gt;What needs testing&lt;/em&gt; is up to individual developers’ discretion, and we avoid adding redundant or pointless tests to the test suite just as we avoid redundant test &lt;em&gt;runs&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Make it fast, remember? That applies to local checks, too. Fast checks get run. Slow checks? No-one has time for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated CI
&lt;/h2&gt;

&lt;p&gt;Changes pushed from local development to our central repository trigger the next layer of checks in the CI pipeline. Feedback here is slower than in local development but still fairly fast, requiring about 10 minutes to run all tests and produce a viable build.&lt;/p&gt;

&lt;p&gt;Here’s what it looks like in Github:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RCKoTqSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2Avc7YZRoU-4OxVaEjHrhDZg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RCKoTqSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2Avc7YZRoU-4OxVaEjHrhDZg.png" alt="Screenshot of automated tests passing in Github’s UI" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Green checks are good checks.&lt;/p&gt;

&lt;p&gt;There are several things going on here: repeats of the linting and static analysis run locally, a run through our completed &lt;code&gt;backend&lt;/code&gt; test suite, and deployment of artifacts used in manual QA. The other checks are variations on this theme—different scripts poking and prodding the commit from different angles to ensure it's ready for merging into &lt;code&gt;main&lt;/code&gt;. Depending on the nature of the change, we may require up to a dozen checks to pass before the commit is greenlit for merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Peer review
&lt;/h2&gt;

&lt;p&gt;In tandem with the automated CI checks, we require manual review and sign-off before changes can be merged into &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;“Manual!?” I hear the purists cry, and yes — the “M” word runs counter to the platonic ideal of totally automated CI. Hear me out. The truth is that every step in our CI/CD pipeline existed as a manual process first. Automating something before truly understanding it is a sure path to inappropriate abstractions, maintenance burden, and at least a few choice words from future generations. And it doesn’t always make sense. For processes that are and always will be dominated by exceptions (design review and acceptance testing, to pick two common examples) we’ve traded any aspirations at full automation for tooling that &lt;em&gt;enables&lt;/em&gt; manual review. We don’t expect to change this any time soon.&lt;/p&gt;

&lt;p&gt;Manual review for us consists of (required) code review and (optional) design review. Code review covers a &lt;a href="https://github.com/rjz/code-review-checklist"&gt;checklist&lt;/a&gt; of logical, quality, and security concerns, and we (plus Github &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches"&gt;branch protection&lt;/a&gt;) require &lt;em&gt;at least&lt;/em&gt; two team members to believe a change is a good idea before we ship it. Besides collective ownership, it’s also a chance to apply a modicum of QA and build shared understanding around what’s changing in the codebase. Ideally, functional issues that weren’t caught locally get caught here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design review
&lt;/h2&gt;

&lt;p&gt;Design review is typically run in tandem with our counterparts in product and design, and aims to ensure that designs are implemented to spec. We provide two channels for reviewing changes before a pull request is merged:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; preview builds of &lt;a href="https://dev.to/developing-koan/routing-on-the-edge-913eb00da742"&gt;a “live” application&lt;/a&gt; that reviewers can interact with directly&lt;/li&gt;
&lt;li&gt; &lt;a href="https://storybook.js.org/"&gt;storybook&lt;/a&gt; builds that showcase specific UI elements included within the change&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both the preview and storybook builds are linked from Github’s pull request UI as soon as they’re available. They also nicely illustrate the type of tradeoffs we’ve frequently made between complexity (neither build is trivial to set up and maintain), automation (know what would be trickier? Automatic visual regression testing, that’s what) and manual enablement (the time we &lt;em&gt;have&lt;/em&gt; decided to invest has proven well worth it).&lt;/p&gt;

&lt;p&gt;The bottom line is that — just like with code review — we would prefer to catch design issues while pairing up with the designer during initial development. But if something slipped through, design review lets us respond more quickly than at stages further down the line.&lt;/p&gt;

&lt;p&gt;The feedback from manual review steps is still available quickly, though: generally within an hour or two of a new pull request being opened. And then it’s on to our staging environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous delivery to staging
&lt;/h2&gt;

&lt;p&gt;Merging a pull request into our &lt;code&gt;main&lt;/code&gt; branch finally flips the coin from continuous integration to continuous delivery. There's one more CI pass first, however: since we identify builds by the commit hash they're built from, a merge commit in &lt;code&gt;main&lt;/code&gt; triggers a new CI run that produces the build artifact we deliver to our staging environment.&lt;/p&gt;

&lt;p&gt;The process for vetting a staging build is less prescriptive than for the stages that precede it. Most of the decision around how much QA or acceptance testing to run in staging rests with the developer on-call (who doubles as &lt;a href="https://dev.to/developing-koan/making-the-most-of-our-startups-on-call-rotation-8769a110c24c"&gt;our de-facto release manager&lt;/a&gt;), who will review a list of changes and call for validation as needed. A release consisting of well-tested refactoring may get very little attention. A major feature may involve multiple QA runs and pull in stakeholders from our product, customer success, and marketing teams. Most releases sit somewhere in the middle.&lt;/p&gt;

&lt;p&gt;Every staging release receives at least passing notice, for the simple reason that we use Koan ourselves — and specifically, an instance hosted in the staging environment. We eat our own dogfood, and a flavor that’s always slightly ahead of the one our customers are using in production.&lt;/p&gt;

&lt;p&gt;Staging feedback isn’t without hiccups. At any time we’re likely to have 3–10 feature flags gating various in-development features, and the gap between staging and production configurations can lead to team members reporting false positives on features that aren’t yet ready for release. We’ve also invested in internal tooling that allows team members to adopt a specific production configuration in their local or staging environments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H-6vJWX4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2Ali06crRg2vyrgmRUZMCAaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H-6vJWX4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2Ali06crRg2vyrgmRUZMCAaw.png" alt="Internal UI for forcing production configuration in staging environment — the design team loves this one." width="800" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The aesthetics are edgy (controversial, even), but the value is undeniable. We’re able to freely build and test features prior to production release, and then easily verify whether a pre-release bug will actually manifest in the production version of the app.&lt;/p&gt;

&lt;p&gt;If you’re sensing that issues caught in staging are more expensive to diagnose and fix than those caught earlier on, you’d be right. Feedback here is much slower than at earlier stages, with detection and resolution taking up to several hours. But issues caught in staging are still much easier to address before they’re released to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual release to production
&lt;/h2&gt;

&lt;p&gt;The “I” in CI is unambiguous. Different teams may take “integration” to mean different things — note the inclusion of critical-if-not-exactly-continuous manual reviews in our own integration process — but “I” always means “integration.”&lt;/p&gt;

&lt;p&gt;The “D” is less straightforward, standing in (depending on who you’re talking to, the phase of the moon, and the day of the week) for either “Delivery” or “Deployment,” and t&lt;a href="https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment"&gt;hey’re not quite the same thing&lt;/a&gt;. We’ve gained enormous value from Continuous Delivery. We haven’t made the leap (or investment) to deploy directly to production.&lt;/p&gt;

&lt;p&gt;That’s a conscious decision. Manual QA and acceptance testing have proven tremendously helpful in getting the product right. Keeping a human in the loop ahead of production helps ensure that we connect with relevant stakeholders (in product, growth, and even key external accounts) prior to our otherwise-frequent releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing in production
&lt;/h2&gt;

&lt;p&gt;As the joke goes, we test comprehensively: all issues missed by our test suite will be caught in production. There aren’t many of these, fortunately, but a broad enough definition of testing ought to encompass the instrumentation, monitoring, alerting, and customer feedback that help us identify defects in our production environment.&lt;/p&gt;

&lt;p&gt;We’ve previously shared an outline of our &lt;a href="https://dev.to/developing-koan/making-the-most-of-our-startups-on-call-rotation-8769a110c24c"&gt;cherished (seriously!) on-call rotation&lt;/a&gt;, and the instrumentation beneath it is a discussion for another day, but suffice to say that an issue caught in production takes much longer to fix than one caught locally. Add in the context-switching required from team members who have already moved on to other things, and it’s no wonder we’ve invested in catching issues earlier on!&lt;/p&gt;

&lt;h2&gt;
  
  
  Revising the pipeline
&lt;/h2&gt;

&lt;p&gt;Increasing velocity means adding people, reducing friction, or (better yet) both. &lt;a href="https://dev.to/@rjzaworski/sharing-our-startups-hiring-manual-efe2094a180c"&gt;Hiring is a general problem&lt;/a&gt;. Friction is specific to the team, codebase, and pipeline in question. We &lt;a href="https://dev.to/developing-koan/porting-koans-150-000-line-javascript-codebase-to-typescript-b4818ccc42ac"&gt;adopted TypeScript&lt;/a&gt; to shorten feedback cycles (and &lt;a href="https://rjzaworski.com/2019/05/making-the-case-for-typescript"&gt;save ourselves runtime exceptions&lt;/a&gt; and &lt;a href="https://dev.to/developing-koan/making-the-most-of-our-startups-on-call-rotation-8769a110c24c"&gt;pagerduty incidents&lt;/a&gt;). That was an easy one.&lt;/p&gt;

&lt;p&gt;A less obvious bottleneck was how much time our pull requests were spending waiting for code review — on average, around 26 hours prior to merge. Three and half business days. On &lt;em&gt;average&lt;/em&gt;. We were still deploying several times per day, but with several days’ worth of work-in-process backed up in the queue and plenty of context switching whenever it needed adjustment.&lt;/p&gt;

&lt;p&gt;Here’s how review times tracked over time:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QFvpxX2L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2AHT9-h2VyhUxZSXmMYsbQKA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QFvpxX2L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1400/1%2AHT9-h2VyhUxZSXmMYsbQKA.png" alt="Chart of code review time showing significant drop on March 2021" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This chart is fairly cyclical, with peaks and troughs corresponding roughly to the beginning and end of major releases — big, controversial changes as we’re trailblazing a new feature; smaller, almost-trivial punchlist items as we close in on release day. But the elephant in the series lands back around March 1st. That was the start of Q2, and the day we added “Code Review Vitals” to our dashboard.&lt;/p&gt;

&lt;p&gt;It’s been said that sunlight cures all ills, and simply measuring our workflow had the dual effects of revealing a significant bottleneck &lt;em&gt;and&lt;/em&gt; inspiring the behavioral changes needed to correct it.&lt;/p&gt;

&lt;p&gt;Voilá! More speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By the time you read this post, odds are that our CI/CD pipeline has already evolved forward from the state described above. Iteration applies as much to process as to the software itself. We’re still learning, and — just like with new features — the more we know, and the sooner we know it, the better off we’ll be.&lt;/p&gt;

&lt;p&gt;With that, a humble question: what have you learned from your own CI/CD practices? Are there checks that have worked (or totally flopped) that we should be incorporating ourselves?&lt;/p&gt;

&lt;p&gt;We’d love to hear from you!&lt;/p&gt;

</description>
      <category>ci</category>
      <category>continuousdelivery</category>
      <category>devops</category>
      <category>tdd</category>
    </item>
    <item>
      <title>Every Software Team Deserves a Charter</title>
      <dc:creator>RJ Zaworski</dc:creator>
      <pubDate>Thu, 02 Dec 2021 16:26:59 +0000</pubDate>
      <link>https://forem.com/koan/every-software-team-deserves-a-charter-1dl0</link>
      <guid>https://forem.com/koan/every-software-team-deserves-a-charter-1dl0</guid>
      <description>&lt;p&gt;Software teams own features, projects, and services—everyone knows that. But caught up in what we’re doing, it’s easy to lose sight of why we’re doing it. Digging into the details might turn up some clues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why does our team exist? To maintain Service X&lt;/li&gt;
&lt;li&gt;Why does Service X need to run? Because it’s a tier-two service that’s a dependency of tier-one Services A and Y&lt;/li&gt;
&lt;li&gt;What happens if Service A goes down? I’m not sure, but it doesn’t sound good.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teams exist for a reason, opaque thought it may be, and team members should know what it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teams are works-in-progress
&lt;/h2&gt;

&lt;p&gt;Clarity is even harder to come by when a team is just starting out. Wouldn’t it be nice if teams arrived in the world as the ancient Greek poet Hesiod describes the birth of the goddess Athena?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;And the father of men and gods gave her birth by way of his head…arrayed in arms of war. —Hesiod, Theogeny&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Anyone who has collaborated with other people can confirm that—unlike ancient Greek deities—teams do not spring forth fully-formed. Nor are they the product of a single head: teams are made up of individuals, and even with a cohesive vision of the team’s objective (not itself a given), everyone likely won’t agree on the best way to get there.&lt;/p&gt;

&lt;p&gt;The psychologist Bruce Tuckman framed this reality with a four-stage model for group development. In Tuckman’s model, teams form, storm, norm, and perform, with high-performing teams emerging only after weather the turbulence of the early stages. But while we can’t control the sequence of events, we can hasten the journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accelerating team formation
&lt;/h2&gt;

&lt;p&gt;Shared expectations are the foundation underlying all effective teamwork. Yet often the process of establishing them is left up to chance. It’s true that time and good intentions usually lead to common ground, but by forcing explicit conversations about the team’s intentions and beliefs up front, a written charter can significantly accelerate the process.&lt;/p&gt;

&lt;p&gt;At a minimum, a charter should lay out the team’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;mission statement&lt;/strong&gt;, summarizing the team’s shared purpose&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;principles&lt;/strong&gt; for conduct and decision-making&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;key performance indicators&lt;/strong&gt; (KPIs) representing the team’s status and health&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While a team lead or manager may write the first draft, revisions are highly encouraged: input and feedback from all team members will only help ensure the charter represents a common understanding of the team’s identity. Ideally the charter-drafting process will start to build collective ownership as well.&lt;/p&gt;

&lt;p&gt;Let’s dig into specifics, and look at how we’ve addressed them in the &lt;a href="https://koan.co"&gt;Koan&lt;/a&gt; dev team charter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mission statement
&lt;/h2&gt;

&lt;p&gt;A charter’s mission statement is a single sentence summarizing what the team does, for whom, and how. Rather than serving specific customer personas or internal stakeholders, our development team is on the hook to advance the company and its mission as a whole. We do it by shipping reliable software and holding ourselves (and our colleagues) to high standards. As the mission statement at the top of our charter reads, we exist:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To advance Koan through engineering excellence and continuous improvement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Principles
&lt;/h2&gt;

&lt;p&gt;Principles are the guidelines that the team can fall back on when assessing contradictory or otherwise unclear choices. They also set expectations. A principles that we, “win the marathon” both encourages thoughtful, long-term decision-making and implies that team members will do it.&lt;/p&gt;

&lt;p&gt;The team’s principles should be short and memorable. In creating our own charter, we brainstormed, debated, and revised our way down to just four. They’re both a clear expression of our common values and simple enough to remember. They read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In support of our company Mission, Vision and Goals, and Values, Koan engineers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Figure it out. We find a way to deliver our objectives while continuously improving along the way.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ship to learn. We release the moment that staging is better than prod, listen early and often, and move faster because of it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deliver customer value. Our work directly benefits our customers — whether they’re outside Koan or at the next desk down.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Win the marathon. We’re in it for the long haul, making decisions that balance today’s needs against the uncertain future ahead.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once again, our closeness to the rest of the company shows through in a brief preamble connecting our team-specific principles back to the mission, vision, and values of the organization as a whole.&lt;/p&gt;

&lt;h2&gt;
  
  
  KPIs
&lt;/h2&gt;

&lt;p&gt;The team’s charter should include a measurable definition of its health. Is the team maintaining basic responsibilities and expectations? Team members should always be able to reference KPIs that quantify its current status.&lt;/p&gt;

&lt;p&gt;As with the mission and principles, the specific metrics will vary considerably across functions. While a sales org may be looking at calls per rep or the total value of qualified leads, a dev team will often focus on the “—ilities”—stability, durability, and so on.&lt;/p&gt;

&lt;p&gt;Our own KPIs are split between numbers we’re interested in (but not actively losing sleep over) and numbers that really matter. The latter are important enough to take up precious real estate on our company dashboard, and as the lone development team in a dynamic startup we’ve limited our focused to just two themes with very specific measures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quality&lt;/strong&gt;: % TypeScript coverage (FE, BE)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Velocity&lt;/strong&gt;: PR lifetime (time delta from opened to merged)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are plenty of other numbers we’re interested in—but for the charter (2021 edition) those two were disproportionately more important to our continued improvement (and quality of life) as a team.&lt;/p&gt;

&lt;h2&gt;
  
  
  The team evolves. The charter, too.
&lt;/h2&gt;

&lt;p&gt;Existing teams need charters, too, and chances are they aren’t the same as when the team was first formed. Explicit or otherwise, the charter will change. It &lt;em&gt;should&lt;/em&gt; change. Revisit it quarterly, revise it yearly, or whenever:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A principle needs updating to reflect changing expectations or operating conditions&lt;/li&gt;
&lt;li&gt;A KPI is significantly exceeded, or becomes an automatic part of the culture&lt;/li&gt;
&lt;li&gt;Team members join or leave&lt;/li&gt;
&lt;li&gt;And so on!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, the charter is just the beginning. So much more goes into an effective team, from the skills individual team members bring to the goals they work together to achieve.&lt;/p&gt;

&lt;p&gt;Even as expectations change over the team’s lifetime, team members should never lack clarity on why the team exists, or on how they can show up and contribute!&lt;/p&gt;

</description>
      <category>teamwork</category>
      <category>leadership</category>
      <category>management</category>
      <category>teams</category>
    </item>
    <item>
      <title>Making the most of our startup’s on-call rotation</title>
      <dc:creator>RJ Zaworski</dc:creator>
      <pubDate>Tue, 16 Nov 2021 00:19:39 +0000</pubDate>
      <link>https://forem.com/koan/making-the-most-of-our-startups-on-call-rotation-3mnh</link>
      <guid>https://forem.com/koan/making-the-most-of-our-startups-on-call-rotation-3mnh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Will I have to be on call?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the last hour of &lt;a href="https://koan.co/"&gt;Koan&lt;/a&gt;’s &lt;a href="https://www.koan.co/blog/why-we-open-sourced-our-hiring-manual"&gt;on-site interview&lt;/a&gt; we turn the tables and invite candidates to interview our hiring team. At face value it’s a chance to answer any open questions we haven’t answered earlier in the process. It’s also a subtle way to introspect on our own hiring process — after three rounds of interviews and side-channel conversations with the hiring manager, what have we missed? What’s on candidates’ minds? Can we address it earlier in the process?&lt;/p&gt;

&lt;p&gt;So, you asked, will I have to be on call?&lt;/p&gt;

&lt;p&gt;The middle of the night pager rings? The panicked investigations? Remediation, write-ups, post-mortems?&lt;br&gt;
We get it. We’ve been there. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/patrickc"&gt;Patrick Collison&lt;/a&gt;’s been there, too:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1432731774270906369-537" src="https://platform.twitter.com/embed/Tweet.html?id=1432731774270906369"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1432731774270906369-537');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1432731774270906369&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;“Don’t ruin the duck.” There are worse guiding principles for an on-call process (and operational health generally).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;So, will I have to be on call?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yeah, you will. But we’ve gotten a ton out of Koan’s on-call rotation and we hope you will, too. Ready to learn more?&lt;/p&gt;

&lt;h2&gt;
  
  
  On-call at Koan
&lt;/h2&gt;

&lt;p&gt;We set up Koan’s on-call rotation before we’d heard anything about Patrick’s ducks. Our version of “don’t ruin the duck,” included three principles that (if somewhat less evocative) have held up surprisingly well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;We concentrate distractions&lt;/strong&gt; — our on-call developer is tasked with minimizing context switching for the rest of the team. We’ll escalate incidents if needed, but as much as possible the business of ingesting, diagnosing, and triaging issues in production services stays in a single person’s hands — keeping the rest of the team focused on shipping great product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We control our own destiny&lt;/strong&gt; — just like Koan’s culture at large, being on-call is much more about results (uptime, resolution time, pipeline throughput, and learning along the way) than how they come about. Our on-call developer wields considerable authority over how issues are fielded and dispatched, and even over the production release schedule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We take turns&lt;/strong&gt; — on-call responsibilities rotate weekly. This keeps everyone engaged with the on-call process and avoids condemning any single person to an eternity (or even an extended period) of pager duty.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These principles have helped us wrangle a fundamentally interrupt-driven process. What we didn’t realize, though, was how much time — and eventually, value — we were recovering between the fire drills.&lt;/p&gt;

&lt;h2&gt;
  
  
  How bugs begin
&lt;/h2&gt;

&lt;p&gt;Before that, though, we’d be remiss to skip the easiest path to a calm, quiet on-call schedule: don’t release. To paraphrase Descartes, code ergo bugs — no matter how diligent you are in QA, shipping software means injecting change (and therefore new defects) into your production environment.&lt;/p&gt;

&lt;p&gt;Not shipping isn’t an option. We’re in the habit of releasing multiple times per day, not to mention all of the intermediate builds pushed to our staging environment via CI/CD. A production issue every now and then is a sign that the system’s healthy; that we’re staying ambitious and shipping fast.&lt;/p&gt;

&lt;p&gt;But it also means that things sometimes break. And when they do, someone has to pick up the phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals
&lt;/h2&gt;

&lt;p&gt;On the bad days, on-call duty is a steady stream of interruptions punctuated by the occasional crisis. On the good days it isn’t much to write home about. Every day, though, there are at least a few minutes to tighten down screws, solve problems, and explore the system’s nooks and crannies. This is an intentional feature (not a bug) of our on-call rotation, and the payoff has been huge. We’ve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;built shared ownership of the codebase and production systems&lt;/li&gt;
&lt;li&gt;systematized logging, metrics, monitoring, and alerting&lt;/li&gt;
&lt;li&gt;built empathy for customers (and our support processes)&lt;/li&gt;
&lt;li&gt;spread awareness of little-used features (we’re always onboarding)&lt;/li&gt;
&lt;li&gt;iterated on key processes (ingestion/triage, release management, etc)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t get all that by just passing around a firefighting hat. You need buy-in and — crucially — a healthy relationship with your production environment. Which brings us back to our principles, and the on-call process that enables it.&lt;/p&gt;

&lt;h2&gt;
  
  
  We concentrate distractions
&lt;/h2&gt;

&lt;p&gt;When something breaks, the on-call schedule clarifies who’s responsible for seeing it’s fixed. As the proverbial umbrella keeping everyone else focused and out of the rain (sometimes a downpour, sometimes a drizzle), you don’t need to immediately fix every problem you see: just to investigate, file, and occasionally prioritize them for immediate attention.&lt;/p&gt;

&lt;p&gt;That still means a great deal of on-call time spent ingesting and triaging a steady drip of symptoms from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer issues escalated by our customer success team&lt;/li&gt;
&lt;li&gt;internal bug reports casually mentioned in conversations, slack channels, or email threads&lt;/li&gt;
&lt;li&gt;exceptions/alerts reported by application and infrastructure monitoring tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes symptoms aren’t just symptoms, and there’s a real issue underneath. Before you know it, the pager starts ringing—&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter the pager
&lt;/h3&gt;

&lt;p&gt;The water’s getting warmer. A pager ping isn’t the end of the world, but we’ve tuned out enough false positives that an alert is a good sign that something bad is afoot.&lt;/p&gt;

&lt;p&gt;Once you’ve confirmed a real issue, the next step is to classify its severity and impact. A widespread outage? Those need attention immediately. Degraded performance in a specific geography? Not awesome, but something that can probably wait until morning. Whatever it is, we’re looking to you to coordinate our response, both externally (updating our status page) and either escalating or resolving the issue yourself.&lt;/p&gt;

&lt;p&gt;On-call isn’t a private island. There will always be times we need to pause work in progress, call in the team, and get to the bottom of something that’s keeping us down. But the goal is to do it in a controlled fashion, holding as much space for everyone else as you reasonably can.&lt;/p&gt;

&lt;h2&gt;
  
  
  We control our own destiny
&lt;/h2&gt;

&lt;p&gt;Your responsibilities aren’t purely reactive, however. Controlling your own destiny means having at least a little agency over what breaks and when. This isn’t just wishful thinking. While issues introduced in the past are always a lurking threat — logical edge cases, bottlenecks, resource limits, and so on — the source of most new issues is a new release.&lt;/p&gt;

&lt;p&gt;It makes sense, then, for whoever’s on-call to have the last word on when (and how) new releases are shipped. This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;managing the release&lt;/strong&gt; — generating changelogs, reviewing the contents of the release, and ensuring the appropriate people are warned and signatures are obtained&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;debugging release / deployment issues&lt;/strong&gt; — monitoring both the deployment and its immediate aftermath, and remediating any issues that arise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;making the call on hotfix releases and rollbacks&lt;/strong&gt; — as a step sideways from our usual flow they’re not tools we use often. But they’re there (and very quick) if you need them&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Closing the feedback loop
&lt;/h3&gt;

&lt;p&gt;An unexpected benefit we’ve noticed from coupling on-call and release management duties is the backpressure it puts on both our release cadence and deployment pipeline. If we’re underwater with issues from the previous release, the release manager has strong incentives to see they’re fixed before shipping anything else. Ditto any issues in our CI/CD processes.&lt;/p&gt;

&lt;p&gt;Neither comes up too often, fortunately, and while we can’t totally write off the combination of robust systems and generally good luck, it’s just as hard to discount the benefits of tight feedback and an empowered team.&lt;/p&gt;

&lt;h2&gt;
  
  
  We take turns
&lt;/h2&gt;

&lt;p&gt;But you said, “team!” — a lovely segue to that last principle. Rotating on-call responsibility helps underscore our team’s commitment to leaving a relatively clean bill (releases shipped, exceptions handled; tickets closed; etc) for the next person up. When you’re on-call, you’re the single person best placed to deflect issues that would otherwise engulf the entire team. When you’re about to be on call, you’re invested in supporting everyone else in doing the same. You’d love to start your shift with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;healthy systems&lt;/li&gt;
&lt;li&gt;a manageable backlog of support inquiries&lt;/li&gt;
&lt;li&gt;a clear list of production exceptions&lt;/li&gt;
&lt;li&gt;a quick brain-dump of issues fielded (and ongoing concerns) from the teammate you’re taking over from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A frequent rotation almost guarantees that everybody’s recently felt the same way. Team members regularly swap shifts (for vacations, appointments, weddings, anniversaries, or any other reason), but it’s never long before you’re back on call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of the time
&lt;/h2&gt;

&lt;p&gt;Ultimately, we’ve arrived at an on-call process that balances the realities of running software in production with a high degree of agency. We didn’t explicitly prioritize quality of life, and we don’t explicitly track how much time on-call duties are eating up. But collective ownership, individual buy-in, and tight feedback have pushed the former up and the latter down, to the point where you’ll find you have considerable time left over for other things. Ideally you’ll use your turn on-call to dig deeper into the issues you touch along the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exploring unfamiliar features (with or without reported bugs)&lt;/li&gt;
&lt;li&gt;tightening up our CI processes&lt;/li&gt;
&lt;li&gt;tuning configurations&lt;/li&gt;
&lt;li&gt;writing regression tests&lt;/li&gt;
&lt;li&gt;improving logging and observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, you’ll be triaging issues, squashing bugs, and maybe even putting out the odd production fire. You can almost count on having time left to help minimize the need for on-call. You’re on the hook to fix things if they break — and empowered to make them better.&lt;/p&gt;

&lt;p&gt;So yes, you’ll have to take an on-call shift.&lt;/p&gt;

&lt;p&gt;Help us make it a good one!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Cover image by &lt;a href="https://unsplash.com/@danielsessler?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Daniel Seßler&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/duck-decoy?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>errors</category>
      <category>ducks</category>
    </item>
    <item>
      <title>From Pebbles to Brickworks: a Story of Cloud Infrastructure Evolved</title>
      <dc:creator>RJ Zaworski</dc:creator>
      <pubDate>Tue, 18 Aug 2020 18:16:23 +0000</pubDate>
      <link>https://forem.com/koan/from-pebbles-to-brickworks-a-story-of-cloud-infrastructure-evolved-3p63</link>
      <guid>https://forem.com/koan/from-pebbles-to-brickworks-a-story-of-cloud-infrastructure-evolved-3p63</guid>
      <description>&lt;p&gt;You can build things out of pebbles. Working with so many unique pieces isn’t easy, but if you slather them with mortar and fit them together just so, it’s possible to build a house that won’t tumble down in the slightest breeze.&lt;/p&gt;

&lt;p&gt;Like many startups, that’s where &lt;a href="https://www.koan.co/?utm_source=dev-to&amp;amp;utm_medium=blog&amp;amp;utm_campaign=terraform-1"&gt;Koan’s&lt;/a&gt; infrastructure started. With lovingly hand-rolled EC2 instances sitting behind lovingly hand-rolled ELBs inside a lovingly — yes — hand-rolled VPC. Each came with its own quirks, software updates, and Linux version. Maintenance was a constant test of our technical acumen and patience (not to mention nerves); scalability was out of the question.&lt;/p&gt;

&lt;p&gt;These pebbles carried us from our earliest prototypes to the first public iteration of Koan’s leadership platform. But there comes a day in every startup’s journey when its infrastructure needs to grow up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivations
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The wolf chased them down the lane and he almost caught them. But they made it to the brick house and slammed the door closed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What we wanted were bricks, uniform commodities that can be replicated or replaced at will. Infrastructure &lt;a href="https://12factor.net/"&gt;built from bricks&lt;/a&gt; has some significant advantages over our pebbly roots:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Visibility&lt;/strong&gt;. Knowing who did what (and when) makes it possible to understand and collaborate on infrastructure. It’s also an absolute must for compliance. Repeatable, version-controlled infrastructure supplements application changelogs with a snapshot of the underlying infrastructure itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence&lt;/strong&gt;. Not knowing — at least, not really knowing — infrastructure makes changes very nervous. For our part, we didn’t. Which isn’t a great position to be in when that infrastructure needs to scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;. Pebbles come in all shapes and sizes. New environment variables, port allocations, permissions, directory structure, and dependencies must be individually applied and verified on each instance. This consumes development time and increases the risk of “friendly-fire” incidents from any inconsistencies between different hosts (see: #2).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeatability&lt;/strong&gt;. Rebuilding a pebble means replicating all of the natural forces that shaped it over the eons. Restoring our infrastructure after a catastrophic failure seemed like an impossible task—a suspicion that we weren’t in a hurry to verify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;. Replacing and extending are two sides of the same coin. While it’s possible to snap a machine image and scale it out indefinitely, an eye to upkeep and our own mental health encouraged us to consider a fresh start. From a minimal, reasonably hardened base image.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since our work at Koan is all about goal achievement, most of our technical projects start exactly where you’d expect. Here: reproducible infrastructure (or something closer to it), documented and versioned as code. We had plenty of expertise with tools like &lt;a href="https://www.terraform.io/"&gt;terraform&lt;/a&gt; and &lt;a href="https://www.ansible.com/"&gt;ansible&lt;/a&gt; to draw on and felt reasonably confident putting them to use—but even with familiar tooling, our initially shaky foundation didn’t exactly discourage caution.&lt;/p&gt;

&lt;p&gt;That meant taking things step by gradual step, establishing and socializing patterns that we intended to eventually adopt across all of our cloud infrastructure. That’s a story for future posts, but the journey had to start somewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev today, tomorrow the world
&lt;/h2&gt;

&lt;p&gt;“Somewhere,” was our trusty CI environment, &lt;code&gt;dev&lt;/code&gt;. Frequent, thoroughly-tested releases are both a reasonable expectation and a point of professional pride for our development team. &lt;code&gt;dev&lt;/code&gt; is where the QA magic happens, and since downtime on &lt;code&gt;dev&lt;/code&gt; blocks review, we needed to keep disruptions to a minimum.&lt;/p&gt;

&lt;p&gt;Before &lt;code&gt;dev&lt;/code&gt; could assume its new form, we needed to be reasonably confident that we could rebuild it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;…in the right VPC&lt;/li&gt;
&lt;li&gt;…with the right Security Groups assigned&lt;/li&gt;
&lt;li&gt;…with our standard logging and monitoring&lt;/li&gt;
&lt;li&gt;…and provisioned with a working instance of the Koan platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four little tests, and we’d have both a repeatable &lt;code&gt;dev&lt;/code&gt; environment and a template we could extend out to production.&lt;/p&gt;

&lt;p&gt;We planned to tackle &lt;code&gt;dev&lt;/code&gt; in two steps. First, we would document (and eventually rebuild) our AWS infrastructure using &lt;code&gt;terraform&lt;/code&gt;. Once we had a reasonably-plausible configuration on our hands, we would then use &lt;code&gt;ansible&lt;/code&gt; to deploy the Koan platform. The two-step approach deferred a longer-term dream of fully-immutable resources, but it allowed us to address one big challenge (the infrastructure) while leaving our existing deployment processes largely intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replacing infrastructure with Terraform
&lt;/h2&gt;

&lt;p&gt;First, the infrastructure. The formula for documenting existing infrastructure in &lt;code&gt;terraform&lt;/code&gt; goes something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a stub entry for an existing resource&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;terraform import&lt;/code&gt; to attach the stub to the existing infrastructure&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;terraform state&lt;/code&gt; and/or &lt;code&gt;terraform plan&lt;/code&gt; to reconcile inconsistencies between the stub and reality&lt;/li&gt;
&lt;li&gt;Repeat until all resources are documented&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s how we documented the &lt;code&gt;dev&lt;/code&gt; VPC's default security group, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ echo '
resource "aws_default_security_group" "default" {
  # hard-coded reference to a resource not yet represented in our
  # Terraform configuration
  vpc_id = var.vpc_id
}' &amp;gt;&amp;gt; main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we could run &lt;code&gt;terraform plan&lt;/code&gt; to see the difference between the existing infrastructure and our Terraform config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ terraform import aws_default_security_group.default sg-123456
$ terraform plan
# module.dev-appserver.aws_default_security_group.default will be updated in-place
  ~ resource "aws_default_security_group" "default" {
      ~ egress                 = [
          - {                                 
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]                    
              - description      = ""
              - from_port        = 0
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "-1"
              - security_groups  = []
              - self             = false
              - to_port          = 0
            },
        ]
        id                     = "sg-123456"
    # ...
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the diff as an outline, we could then fill in the corresponding &lt;code&gt;aws_default_security_group.default&lt;/code&gt; entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# main.tf
resource "aws_default_security_group" "default" {
  vpc_id = var.vpc_id
  ingress {
    protocol  = -1
    self      = true
    from_port = 0
    to_port   = 0
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-running &lt;code&gt;terraform plan&lt;/code&gt;, we could verify that the updated configuration matched the existing resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ terraform plan
...
No changes. Infrastructure is up-to-date.
This means that Terraform did not detect any differences between
your configuration and real physical resources that exist. As a 
result, no actions need to be performed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The keen observer will recognize a prosaic formula crying out for automation, a call we soon answered. But for our first, cautious steps, it was helpful to document resources by hand. We wrote the configurations, parameterized resources that weren’t imported yet, and double-checked (triple-checked) our growing Terraform configuration against the infrastructure reported by the &lt;a href="https://aws.amazon.com/cli/"&gt;aws CLI&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing Terraform state with a small team
&lt;/h2&gt;

&lt;p&gt;By default, Terraform tracks the state of managed infrastructure in a local &lt;a href="https://www.terraform.io/docs/state/index.html"&gt;tfstate&lt;/a&gt; file. This file contains both configuration details and a mapping back to the “live” resources (via IDs, resource names, and in Amazon’s case, ARNs) in the corresponding cloud provider. As a small, communicative team in a hurry, we felt comfortable bucking best practices and checking our state file right into source control. In almost no time we ran into collisions across git branches—a shadow of collaboration and locking problems to come—but we resolved to adopt more team-friendly practices soon. For now, we were up and running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast"&gt;Make it work, make it right&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Provisioning an application with Ansible
&lt;/h2&gt;

&lt;p&gt;With most of our &lt;code&gt;dev&lt;/code&gt; infrastructure documented in Terraform, we were ready to fill it out. At this stage our attention shifted from the infrastructure itself to the applications that would be running on it—namely, the Koan platform.&lt;/p&gt;

&lt;p&gt;Koan’s platform deploys as a monolithic bundle containing our business logic, interfaces, and the small menagerie of dependent services that consume them. Which services run on a given EC2 instance will vary from one to the next. Depending on its configuration, a production node might be running our REST and GraphQL APIs, webhook servers, task processors, any of a variety of cron jobs, or all of the above.&lt;/p&gt;

&lt;p&gt;As a smaller, lighter, facsimile, &lt;code&gt;dev&lt;/code&gt; has no such differentiation. Its single, inward-facing node plays host to the whole kitchen sink. To simplify testing (and minimize the damage to &lt;code&gt;dev&lt;/code&gt;), we took the cautious step of replicating this configuration in a representative local environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a local Amazon Linux environment
&lt;/h2&gt;

&lt;p&gt;Reproducing cloud services locally is tricky. We can’t run EC2 on a developer’s laptop, but Amazon has helpfully shipped &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/amazon-linux-2-virtual-machine.html"&gt;images of Amazon Linux&lt;/a&gt;—our bricks’ target distribution. With a little bit of fiddling and a lot of help from &lt;a href="https://cloudinit.readthedocs.io/"&gt;&lt;code&gt;cloud-init&lt;/code&gt;&lt;/a&gt;, we managed to bring up reasonably representative Amazon Linux instances inside a local &lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ssh -i local/ssh/id_rsa dev@localhost -p2222
Last login: Fri Sep 20 20:07:30 2019 from 10.0.2.2
       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\\___|___|
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we could create an ansible inventory assigning the same groups to our "local" environment that we would eventually assign to &lt;code&gt;dev&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# local/inventory.yml
appservers:
  hosts:
    127.0.0.1:2222
cron:
  hosts:
    127.0.0.1:2222
# ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we did it all over again, we could likely save some time by skipping VirtualBox in favor of a detached EC2 instance. Then again, having a local, fast, safe environment to test against has already saved time in developing new ansible playbooks. The jury’s still out on that one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ansible up!
&lt;/h2&gt;

&lt;p&gt;With a reasonable facsimile of our “live” environment, we were finally down to the application layer. ansible approaches hosts in terms of their roles—databases, webservers, or something else entirely. We approached this by separating out two “base” roles for our VMs generally (&lt;code&gt;common&lt;/code&gt;) and our app servers in particular (&lt;code&gt;backend&lt;/code&gt;), where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;common&lt;/code&gt; role described monitoring, the runtime environment, and a default directory structure and permissions&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;backend&lt;/code&gt; role added a (verioned) release of the Koan platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additional roles layered on top represent each of our minimally-dependent services — &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;tasks&lt;/code&gt;, &lt;code&gt;cron&lt;/code&gt;, and so on—which we then assigned to the local host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# appservers.yml 
- hosts: all
  roles:
  - common
  - backend
- hosts: appservers
  roles:
  - api
- hosts: cron
  roles:
  - cron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We couldn’t bring EC2 out of the cloud, but bringing up a local instance that quacked a &lt;em&gt;lot&lt;/em&gt; like EC2 was now as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ansible-playbook \
  --user=dev \
  --private-key ./local/ssh/id_rsa \
  --inventory local/inventory.yml \
  appservers.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  From pebbles to brickwork
&lt;/h2&gt;

&lt;p&gt;With our infrastructure in &lt;code&gt;terraform&lt;/code&gt;, our deployment in &lt;code&gt;ansible&lt;/code&gt;, and all of the confidence that local testing could buy, we were ready to start making bricks. The plan (and there’s always a plan!) was straightforward enough:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use terraform apply to create a new dev instance&lt;/li&gt;
&lt;li&gt;Add the new host to our ansible inventory and provision it&lt;/li&gt;
&lt;li&gt;Add it to the dev ELB and wait for it to join (assuming provisioning succeeded and health checks passed)&lt;/li&gt;
&lt;li&gt;Verify its behavior and make adjustments as needed&lt;/li&gt;
&lt;li&gt;Remove the old dev instance (our pebble!) from terraform&lt;/li&gt;
&lt;li&gt;Rinse and repeat in production&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire process was more hands-on than anyone really wanted, but given the indeterminate state of our existing infrastructure and the guiding philosophy of, step one was simply waving &lt;code&gt;dev&lt;/code&gt; out the door.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Make it work, make it right.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Off it went! With only a little back and forth to sort out previously unnoticed details, our new &lt;code&gt;dev&lt;/code&gt; host took its place as brick #1 in Koan’s growing construction. We extracted the &lt;code&gt;dev&lt;/code&gt; configuration into a reusable &lt;code&gt;terraform&lt;/code&gt; module and by the end of the week our brickwork stretched all the way out to production.&lt;/p&gt;

&lt;p&gt;In our next post, we'll dive deeper into how we imported volumes of undocumented infrastructure into Terraform.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Big thanks to &lt;a href="https://twitter.com/swac"&gt;Ashwin Bhat&lt;/a&gt; for early feedback, &lt;a href="https://twitter.com/randallagordon?lang=en"&gt;Randall Gordon&lt;/a&gt; and &lt;a href="https://twitter.com/andrewbeers?lang=en"&gt;Andy Beers&lt;/a&gt; for helping turn the &lt;a href="https://devops.stackexchange.com/questions/653/what-is-the-definition-of-cattle-not-pets"&gt;pets/cattle metaphor&lt;/a&gt; into something more humane, and &lt;a href="https://unsplash.com/@emardi?utm_source=dev-to&amp;amp;utm_medium=referral"&gt;EMAR DI&lt;/a&gt; on &lt;a href="https://unsplash.com/?utm_source=medium&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt; for the cover image.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And if you’re into building software to help every team achieve its objectives, &lt;a href="https://www.koan.co/company/careers?utm_source=dev-to&amp;amp;utm_medium=blog&amp;amp;utm_campaign=terraform-1"&gt;Koan is hiring&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>terraform</category>
      <category>ansible</category>
    </item>
    <item>
      <title>High-Value Software Testing</title>
      <dc:creator>RJ Zaworski</dc:creator>
      <pubDate>Sat, 11 Jul 2020 16:37:44 +0000</pubDate>
      <link>https://forem.com/rjz/high-value-software-testing-31dc</link>
      <guid>https://forem.com/rjz/high-value-software-testing-31dc</guid>
      <description>&lt;p&gt;Tests help developers eliminate defects, build confidence, practice good design, and ideally all three. They also take time to write, run, and update--time that's no longer available for other development tasks.&lt;/p&gt;

&lt;p&gt;High-value testing seeks to maximize the return on that investment. Like much of software development, it's as much art as science. But a few practical principles can help keep things pointed in the right direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The golden rule of high-value testing
&lt;/h2&gt;

&lt;p&gt;Remember: the aim of software testing—the most fundamental reason to bother—is to &lt;em&gt;secure the value the software creates&lt;/em&gt;. If a test doesn't secure value, it's better having no test at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the right things
&lt;/h2&gt;

&lt;p&gt;The relative benefit of additional testing tapers off as coverage nears 100%. In a safety-critical application the conversation may &lt;em&gt;begin&lt;/em&gt; at 100%--but in less-risky settings, there are pragmatic arguments against such exhaustive testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test what you must (and only what you must)
&lt;/h3&gt;

&lt;p&gt;The familiar challenge of balancing quality, scope, and time (pick two) applies to testing, too. As low-quality tests are worse than useless and software has &lt;em&gt;no&lt;/em&gt; value until it ships, the compromise usually comes down to scope.&lt;/p&gt;

&lt;p&gt;If there’s only budget for a single test, the highest return will come from verifying that the positive, “happy path” is working as intended. From there, tests can expand to cover the negative cases as risk requires and budget allows.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do tests inspire an appropriate level of confidence in our application?&lt;/li&gt;
&lt;li&gt;Is testing slowing upfront development?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Focus on the interface
&lt;/h3&gt;

&lt;p&gt;All the outside world knows about a function, class, or module is its public interface. Tests should begin and end there.&lt;/p&gt;

&lt;p&gt;As software evolves, its internal implementation should be free to follow. Testing takes time. Testing details that don’t impact outward behavior is inefficient; worse, it can confuse and discourage future changes. Internal logic that can be extracted into a generally-useful unit may deserve a test of its own—but if not, and if the interface above it is working as intended, don’t lose sleep over what’s happening beneath.&lt;/p&gt;

&lt;p&gt;Testing is also an opportunity both to &lt;a href="https://dev.to/2018/04/five-better-comments"&gt;show how the interface is used&lt;/a&gt; and to actually &lt;em&gt;use&lt;/em&gt; it. Every test is an extra chance to smooth out a rough edge before it can snag a customer. Afterwards, it’s &lt;a href="https://dev.to/2015/05/the-documentation-youve-been-looking-for"&gt;living documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do tests reference internal methods or configuration settings that wouldn’t be seen in normal operations?&lt;/li&gt;
&lt;li&gt;Do tests reflect how the interface should be used?&lt;/li&gt;
&lt;li&gt;Do tests document both the interface’s use cases and error states?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test in layers
&lt;/h3&gt;

&lt;p&gt;No matter the slope of the &lt;a href="https://martinfowler.com/bliki/TestPyramid.html"&gt;testing pyramid&lt;/a&gt;, separating the zippy, independent tests that verify local behavior from the hefty specs securing the assembled system will improve the responsiveness and value of both. The goal is a reliable test suite, yes, but also one that encourages a fast feedback cycle. Nobody wants to wait through a treacle-slow end-to-end run just to confirm that a unit test passes, nor should that end-to-end run shouldn’t spend much time repeating unit test assertions.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is it easy to delineate between lightweight unit tests and more-expensive functional or end-to-end tests?&lt;/li&gt;
&lt;li&gt;Are functional or end-to-end tests repeating details that should be captured by unit tests?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Trust proven dependencies
&lt;/h3&gt;

&lt;p&gt;Any open-source library worth its salt will ship with its own community-maintained test suite. It shouldn’t require additional testing in userland. Trusting the community’s experience (and contributing patches upstream if that faith proves misplaced) means more time to test business logic and raise the value of the test suite as a whole.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are dependencies in widespread use by the community?&lt;/li&gt;
&lt;li&gt;Are tests spending time re-asserting “known-good” behavior in dependencies?&lt;/li&gt;
&lt;li&gt;Can gaps in a dependency’s test suite be contributed upstream?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mock what you must (but nothing more)
&lt;/h3&gt;

&lt;p&gt;Most of the parts within a reasonably complex software application are both dependent on and dependencies of other parts of the same. It’s common for tests to supply mocks—test-specific implementations of external interfaces—to help isolate unit behavior. But see the problem? If the "real" implementation changes, an out-of-date mock will still allow tests to pass. Even a correct mock represents new code to write, verify, and maintain--who will be testing that?&lt;/p&gt;

&lt;p&gt;If a mock isn't needed, don’t write it. Don’t be shy about refactoring to &lt;em&gt;make&lt;/em&gt; it unnecessary, either. Mocks often crop up when logic and I/O are tightly coupled; in many cases decoupling them will lead to more reusable design (and simpler testing) than supplying internal behavior via a test mock.&lt;/p&gt;

&lt;p&gt;If a mock &lt;em&gt;is&lt;/em&gt; needed, though:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep it simple (most testing utilities provide canonical tools to help with this).&lt;/li&gt;
&lt;li&gt;Keep it clean (don’t use mocks to store state, and don’t share mocks between tests)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can logic be tested independent of external behavior?&lt;/li&gt;
&lt;li&gt;Are mocks implemented using tools provided by a test utility or the language’s standard library?&lt;/li&gt;
&lt;li&gt;What assumptions are represented (and upheld) by each mock?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Inspire confidence
&lt;/h2&gt;

&lt;p&gt;Tests that can't be trusted are worse than no tests at all. Energy invested in both their reliability and depiction of reality will always, always pay off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Different run, same result
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;It works on my machine&lt;br&gt;
-- anonymous&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A test suite should reach the same conclusions no matter who’s running it. For unit tests this means avoiding dependencies on network, I/O, the local timezone, and other tests. Deeper, end-to-end tests should proceed from a consistent, isolated state—shared datastores or service dependencies are a recipe for heartache when multiple test runs are proceeding in parallel in a continuous integration environment.&lt;/p&gt;

&lt;p&gt;Tests should isolate a single variable, poke it, and see how it responds. If other things are poking it at the same time, it takes some seriously fancy filters and statistical magic--or more likely a sad, sad playthrough of the run-it-until-it-passes game--to get back to the way things were working before.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do tests interact with a database, filesystem, the network interface, or perform any other (potentially-fraught) I/O?&lt;/li&gt;
&lt;li&gt;Do tests depend on or otherwise interact with other tests?&lt;/li&gt;
&lt;li&gt;Will a time-dependent test produce the same output when run in another timezone?&lt;/li&gt;
&lt;li&gt;Will the same test pass if it’s run on another operating system / filesystem?&lt;/li&gt;
&lt;li&gt;Does logic depend on random number-generators or any other non-deterministic state?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Non-deterministic tests clog development workflows and breed mistrust in the test suite generally. Fix them promptly. Better yet, avoid writing them in the first place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test reality
&lt;/h3&gt;

&lt;p&gt;Production is the one place in the Universe that really, truly quacks like production. There’s something to be said for testing there, particularly around performance-related issues, but it’s also a very expensive place to get things wrong.&lt;/p&gt;

&lt;p&gt;Given the hefty operational risk of testing in production, it’s usually best to substitute a reasonable facsimile instead. The application configuration and operating environment should be as “prod-like” as possible, even if system parameters are beefed up or the underlying dataset is slimmed down in the name of developer experience.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where does the test environment deviate from production?&lt;/li&gt;
&lt;li&gt;What production-specific issues aren’t (or can’t be) adequately covered in the test suite?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Human factors
&lt;/h2&gt;

&lt;p&gt;A comprehensive test suite is a worthy aspiration with diminishing returns. Squeezing the most from it requires a careful balance between the value it secures and its upkeep cost. A good test suite isn’t merely complete: it’s also relatively straightforward to modify or run. Tests that are easy to write, verify, and adjust encourage quick iteration and deliver more value. And tests that no-one wants to touch…&lt;/p&gt;

&lt;p&gt;One lens for assessing upkeep is the time needed to modify an existing test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explain failures
&lt;/h3&gt;

&lt;p&gt;If an assertion fails, it shouldn’t take an expedition to explain where or why. An easy first step towards clear testing (itself an art form) is to make sure the runner or test description highlights the test’s subject and motivation.&lt;/p&gt;

&lt;p&gt;For unit tests, identify the test’s subject and what the test is meant to prove. This might be a module, class, or method—it might even be assigned by convention in the test harness. Next, explain the motivation. What is the test out to achieve?&lt;/p&gt;

&lt;p&gt;For higher-level integration or end-to-end testing, it may be more appropriate to describe tests in terms of the actors involved and their progress through a predefined test script. Again, any failures should make it clear what the actor was trying to accomplish and where their expectations weren’t met.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do failed tests clearly indicate what was being tested?&lt;/li&gt;
&lt;li&gt;Do failed tests clearly indicate what failed?&lt;/li&gt;
&lt;li&gt;Do test descriptions follow a predictable format throughout the test suite?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  No test is sacred
&lt;/h3&gt;

&lt;p&gt;Dogmatic test-driven developers test first. Less committed developers may test later, or not at all. But whether starting with the tests or adding them after the fact, there’s no reason to hold onto tests that have outlived their usefulness. As logic changes, tests may become irrelevant, redundant, or outright misleading. It’s tricky—often dangerous—to throw away legacy code that isn’t well understood. But if a test doesn’t make sense even after a modest investigation, the world will be a better place with one less mystery in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When in doubt, delete
&lt;/h2&gt;

&lt;p&gt;It’s not only true in testing, but a certain sort of ruthlessness goes into keeping up a high-value test suite. Tests create drag, case closed. A thorough test suite takes longer to run; builds are slower to build, and changes take longer to make. That’s a good thing when something needs to behave in a very specific way, but in less-critical code it can simply slow down the speed of other, necessary development.&lt;/p&gt;

&lt;p&gt;Focus on value. Test accordingly. If the value isn’t there, or if the opportunity costs exceed the perceived benefits, it’s time for tests to go. They can always come back later, but odds are that you won’t even miss them.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>tdd</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Damage Control in Distributed Systems</title>
      <dc:creator>RJ Zaworski</dc:creator>
      <pubDate>Sat, 25 Jan 2020 15:04:27 +0000</pubDate>
      <link>https://forem.com/rjz/damage-control-in-distributed-systems-2a9b</link>
      <guid>https://forem.com/rjz/damage-control-in-distributed-systems-2a9b</guid>
      <description>&lt;p&gt;It began with a squirrel, judging by the tail, an ill-conceived violation of the transformer's inner sanctum greeted with righteous thunder and an otherwise minor blip across the United Illuminating Company's grid. What &lt;a href="https://www.nytimes.com/1987/12/10/business/stray-squirrel-shuts-down-nasdaq-system.html"&gt;took down the mighty Nasdaq exchange&lt;/a&gt; wasn't the incident itself, but what came after. Not the blaze of glory or the grieving mother in the family nest on the Pequonnock River, but the surge of power flowing back down the lines.&lt;/p&gt;

&lt;p&gt;Grid operators, &lt;a href="https://rjzaworski.com/2018/10/thinking-in-systems"&gt;systems thinkers&lt;/a&gt;, and grey-beard sysadmins will tell you: systems are hard. Defining, assigning, and synchronizing work is tricky enough on one computer, but even &lt;a href="https://rjzaworski.com/2018/10/simple-tools"&gt;simple tools&lt;/a&gt; can yield &lt;a href="https://en.wikipedia.org/wiki/Abelian_sandpile_model"&gt;surprising complexity&lt;/a&gt; when assembled into networks.&lt;/p&gt;

&lt;p&gt;The failure states that arise in deeply interconnected systems are as difficult to anticipate as they are to resolve, and sometimes the best we can do is simply to stop them from spreading. Keep the rest of the system running? That's a good day. No amount of thoughtful application design will resolve a problem begun somewhere else, but by embracing failure and owning its impact on the user, our services can at least strive not to make things worse.&lt;/p&gt;

&lt;p&gt;What follows are several useful patterns for containing and responding to common faults. They're presented in stripped-down form with neither the configurability nor runtime visibility they would need in production, but they're still a useful place to start. Which is to say: study them, don't use them. &lt;em&gt;Caveat emptor&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The common currency
&lt;/h2&gt;

&lt;p&gt;For the sake of example, let's pretend that every action inside a big, imaginary network can be represented as a function--call it &lt;code&gt;Action&lt;/code&gt;--consisting of an immediate &lt;code&gt;Request&lt;/code&gt; and some eventual &lt;code&gt;Response&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's nothing stopping us from representing future actions as continuations, streams, or your favorite asynchronous programming model, but we'll stick to just one of them here.&lt;/p&gt;

&lt;p&gt;Simple enough? Good. Let's go make things fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time limit
&lt;/h2&gt;

&lt;p&gt;Here's one to ponder: how long can a long-running action go on before the customer (even a very patient, very digital customer) loses all interest in the outcome?&lt;/p&gt;

&lt;p&gt;Pull up a chair. With no upper bound, we could be here a while.&lt;/p&gt;

&lt;p&gt;The way to guarantee service within a reasonable window is to &lt;em&gt;set&lt;/em&gt; that bound. "Service" might mean an error with our end user--but a timely error will almost always be an improvement over waiting until the heat death of the universe. And the implementation works like you'd expect. We'll start a race between a timer and some &lt;code&gt;pending&lt;/code&gt; promise. If the timer comes back first, we'll declare the promise timed out and unblock it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TimeoutError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ETIMEOUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TimeLimiterOpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;timeLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TimeLimiterOpts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;race&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Timed out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's one big caveat, though: while for all intents and purposes we're carrying on as if we've "canceled" the promise, we've (in JavaScript, at least) only &lt;a href="https://github.com/tc39/proposal-cancellation"&gt;canceled the &lt;code&gt;Request&lt;/code&gt;&lt;/a&gt;. We'll see our &lt;code&gt;TimeoutError&lt;/code&gt;, but any resources or computations attached to the &lt;code&gt;pending&lt;/code&gt; promise may still be running to completion, and associated references may not yet be released.&lt;/p&gt;

&lt;p&gt;Cutting off long-running requests is a very sensible step. But what about heading off the sort of congestion that might slow them down in the first place?&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limit
&lt;/h2&gt;

&lt;p&gt;If we've benchmarked our system's performance--and before worrying too much about loaded behavior behavior it's worth benchmarking our system's performance--we'll already have a rough sense of where and how it's likely to fail. With those numbers, we can try to keep traffic below some "known-good" threshold and preempt trouble before it starts. Here's a pared-down implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RateLimiterOpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RateLimiter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;RateLimitError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERATELIMIT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;rateLimiter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RateLimiterOpts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;RateLimiter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;periodStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;periodStart&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;periodStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;RateLimitError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Exceeded rate limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to control invocation, this pattern wraps a &lt;code&gt;Supplier&lt;/code&gt; (in lieu of a single &lt;code&gt;Promise&lt;/code&gt;). By blocking invocations beyond a user-specified &lt;code&gt;limit&lt;/code&gt;, we now have a crude lever to control &lt;code&gt;supplier&lt;/code&gt; throughput.&lt;/p&gt;

&lt;p&gt;This is useful enough, though there's plenty to be done here to improve ease of use. In a more generous implementation we might warn customers as we approach the limit (allowing them to throttle requests accordingly) or even &lt;a href="https://rjzaworski.com/2015/08/circular-queue"&gt;queue rejected requests&lt;/a&gt; to be re-processed later.&lt;/p&gt;

&lt;p&gt;Which raises an interesting question: when failure happens, what comes next?&lt;/p&gt;

&lt;h2&gt;
  
  
  Retry
&lt;/h2&gt;

&lt;p&gt;Well, something broke.&lt;/p&gt;

&lt;p&gt;Maybe it was a self-imposed limit from a timer or rate-limiter, or maybe it was a genuine, honest-to-goodness exception in some action. In either case, we'll need to decide whether to dutifully relay the failure to our customer or first attempt recovery on our own. If the error looks recoverable--a network flaked out, or a service was temporarily unavailable--we'll usually start with the latter.&lt;/p&gt;

&lt;p&gt;The easiest step may be simply to retry it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RetryerOpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;RetryError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERETRY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;retryer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RetryerOpts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;RetryError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Exceeded retry limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again we've spared some niceties in the name of a straightforward example, but that's the heart of it. We watch for an error, and (provided we haven't already exceeded a reasonable retry limit) we'll go ahead and try it again. In a more robust implementation, we would likely want a way to filter "retryable" errors from the recoverable sort, as well as some way to review &lt;em&gt;all&lt;/em&gt; of the errors related to both the initial request and our subsequent retry attempts. We may also want the ability to "cancel" retries in-flight, or even to check in on progress. But those, too, are projects for another day.&lt;/p&gt;

&lt;p&gt;One that's worth taking on, however, is the schedule on which our retries are sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backoff
&lt;/h2&gt;

&lt;p&gt;A retry policy is both a sensible first step and a terrific way to load artificial traffic onto an already-beleaguered service. If a failure may be due to request throttling or an overwhelmed service, it's a good idea (as well as good form) to take a breath before any subsequent retries.&lt;/p&gt;

&lt;p&gt;Here's our original &lt;code&gt;retryer&lt;/code&gt;, this time with room for different backoff strategies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BackoffStrategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RetryerOpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BackoffStrategy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;retryer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RetryerOpts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;RetryError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Exceeded retry limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backoff isn't quite enough on its own, though. Consider the case where a surge in requests causes many clients to fail near-simultaneously. If the clients share a common retry policy, no amount of backing-off will save the coordinated surge that follows with each successive retry.&lt;/p&gt;

&lt;p&gt;What &lt;em&gt;will&lt;/em&gt; save them is to &lt;a href="https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/"&gt;jitter&lt;/a&gt; the backoff calculation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We may still face down the original collision, but a bit of entropy sprinkled on our beautiful, deterministic backoff algorithm will at least prevent the (unintentionally) coordinated sort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Circuit breaker
&lt;/h2&gt;

&lt;p&gt;So far we've put bounds around the sorts of faults that are relatively easy to specify. Requests need to settle within a certain window? Cut them off. Services will fail under a certain load? Limit the volume of requests.&lt;/p&gt;

&lt;p&gt;Not all production faults are so accommodating. We may not know &lt;em&gt;how&lt;/em&gt; something will fail--but when it does, we do want to detect it and avoid making it worse.&lt;/p&gt;

&lt;p&gt;Enter the circuit breaker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CircuitBreakerOpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;waitDuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CircuitBreaker&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CircuitOpenError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ECIRCUITOPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLOSED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HALF_OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;circuitBreaker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CircuitBreakerOpts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Supplier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;rejected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLOSED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lastChangeAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;rejected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;lastChangeAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLOSED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastChangeAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitDuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HALF_OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CircuitOpenError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Circuit breaker is open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HALF_OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLOSED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HALF_OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;rejected&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;failureRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rejected&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;failureRate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real world circuit breakers save all sorts of incendiary unpleasantness by cutting off the power to a circuit that's exceeded its design load. Our version will open its "circuit" when a certain signal (in this case, the error rate) exceeds a user-defined &lt;code&gt;threshold&lt;/code&gt;. After waiting in the &lt;code&gt;OPEN&lt;/code&gt; state, the breaker will relax to a &lt;code&gt;HALF_OPEN&lt;/code&gt; position, at which point the success or failure of the next request will either restore normal operations or trip it back &lt;code&gt;OPEN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once again, a production version of the circuit breaker would also some ability to filter "safe" errors, as well as exposing some insight into the present state of the &lt;a href="https://en.wikipedia.org/wiki/Finite-state_machine"&gt;state machine&lt;/a&gt; embedded inside our breaker. We'd likely surface an &lt;code&gt;EventEmitter&lt;/code&gt; or push state changes, failure rates, or both to a metrics collector--just as we'd ship attempts, throughput, and latency from the rest of our fault-tolerance toolkit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it into practice
&lt;/h2&gt;

&lt;p&gt;There are a couple of points worth considering before putting these patterns into practice. First, will the complexity added by any of these patterns solve a pressing problem within your system? If the answer is anything less than certain, leave it out.&lt;/p&gt;

&lt;p&gt;Second, consider whether a proven library like &lt;a href="https://github.com/Netflix/Hystrix"&gt;Hystrix&lt;/a&gt;, &lt;a href="https://github.com/resilience4j/resilience4j"&gt;resilience4j&lt;/a&gt;, (or the port into your favorite language) will provide the features you need. It probably will. Leaning on it will save the trouble of verifying, benchmarking, and ironing out the kinks in your own, homegrown safety equipment.&lt;/p&gt;

&lt;p&gt;If you're tired of &lt;a href="https://en.wikipedia.org/wiki/Finite-state_machine"&gt;chasing mysteries&lt;/a&gt; through a big, teetering network, though, patterns like these can help make its failures more obvious. Maybe you'll combine them--by wrapping a circuit breaker around a rate-limited component, say--to layer on the sanity-checks and fallback behaviors. Maybe they'll live in sidecar containers, in API middleware, or in proxies fronting for serverless functions.&lt;/p&gt;

&lt;p&gt;But whatever and however you use them, the ability to recognize and handle errors is a critical part of coming to terms with the complexity of your distributed system. Assume failure. Own the bad news. And above all, don't make it worse.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>distributedsystems</category>
    </item>
  </channel>
</rss>
