<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>Exploring Better Ways - Bellroy</title>
        <link>https://exploring-better-ways.bellroy.com</link>
        <description><![CDATA[Sharing insight and using business as a force for good]]></description>
        <atom:link href="https://exploring-better-ways.bellroy.com/rss.xml" rel="self"
                   type="application/rss+xml" />
        <lastBuildDate>Fri, 13 Mar 2026 00:00:00 UT</lastBuildDate>
        <item>
    <title>Reflections on Bellroy's Tech Team 2025</title>
    <link>https://exploring-better-ways.bellroy.com/reflections-on-bellroys-tech-team-2025.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Reflections on Bellroy's Tech Team 2025</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/mike_ai_avatar.jpeg"
              alt="Mike Webb profile image"
            />
            <a class="no-underline font-bold" href="/contributors/mike_webb.html">Mike Webb</a>
            <small>2026-03-13</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>We shipped more than ever in 2025, and by the end of the year we were struggling with the complexity
of it all. In writing this post, I continue to be amazed at the amount the Bellroy Technology Team
is able to achieve and support in a year with just 15 staff.</p>
<p>In 2024, we realised our processes had a blind spot. We could see internally-facing projects
that were clearly valuable were not getting the attention they deserved, so we revised our project
evaluation methodology to give these types of projects fairer weight based on the opportunity cost
of <em>not</em> doing them. The result was that, in 2024 and 2025, we split our focus between process
automation for other teams, company-wide tooling, infrastructure modernisation <em>and</em>
customer-facing features. By the end of the year, a recurring theme in our team retrospectives was
that the sheer breadth of what we were maintaining and building had become difficult for any one
person to hold in their head, and yet there was an unspoken expectation that everyone was across
everything.</p>
<p>Prior to each eight week cycle, we evaluate the list of projects that people all over the
business had put forward. Every candidate project goes through a probabilistic
<a href="https://www.investopedia.com/terms/n/npv.asp">NPV</a> model we built internally, which estimates the
value each project is likely to deliver over the next five years. The projects described below are
the ones that scored highest - and even then, we weren’t always wholly successful at delivering
their full value.</p>
<h2 id="what-we-worked-on">What we worked on</h2>
<p>Our most complex project - integrating <a href="https://stripe.com/">Stripe</a> alongside
<a href="https://www.paypal.com/us/braintree">Braintree</a> as our payment provider - taught us the most about
long-tail risk. The project touched every part of our checkout experience and our developers
needed to deeply understand our legacy payment flow to be able to implement it as an A/B test.
We had a long tail of edge cases that only emerged under real traffic; for example, asynchronous
payment types such as <a href="https://www.afterpay.com/">AfterPay</a> behave <em>significantly</em> differently in
the wild from synchronous payment types, such as a credit card payment. This threw up some edge
cases that we had not had to deal with our previous payment provider. Because these issues arose
sometimes long after the project was completed, it either fell on the original project team to
re-load the context to address the issues or it fell to other personnel to resolve the issue
with far less experience with that domain.</p>
<p>Sometimes we understood the business objective and the technical requirements, but hadn’t thought
about the staff-facing UX changes. We rolled out more aggressive <a href="https://aws.amazon.com/cloudfront/">CloudFront</a>
caching across our website, meaningfully improving page load times. While we achieved the desired
performance increase, we didn’t sufficiently communicate the change in site behaviour to our content
team and they were surprised to discover that their previous experience of “instant publishing” now
came with a short lag (while the CloudFront cache was invalidated).</p>
<p>The Technology team have been using AI tools since the early days of Claude Code, and many other
teams at Bellroy were buying per-seat licenses for Claude and ChatGPT on a as-needed basis. By
mid-year these individual user accounts across ChatGPT and Claude were costing us upwards of
USD$3,000 a month. In response to this, we deployed <a href="https://lobechat.com/">LobeChat</a> as a
self-hosted LLM client for the whole company, granting API access to the same models at a fraction
of the cost. We ran roadshow sessions and regular open “AI assistance” sessions for the whole
company as part of the rollout. Now responsible for keeping the tools up to date for a bigger
audience, we were suddenly subjected to the breakneck pace of change in the tool itself and across 3
different model providers.</p>
<p>And those were just the big ticket items - on the customer-facing side, we expanded into new retail
partnerships and marketplaces, and enabled <a href="https://bellroy.com">bellroy.com</a> orders to be
<a href="https://sell.amazon.com.au/fba-mcf">fulfilled via Amazon</a> in more countries. We continued our
tradition of working closely with other teams to automate and improve their processes - inbound
shipment management for Production, corporate sales inquiries for Wholesale, milestone staff rewards
for People &amp; Culture. We undertook a wholesale modernisation of our server infrastructure, migrating
our configuration management from <a href="https://www.chef.io/">Chef</a> to <a href="https://www.puppet.com/">Puppet</a>.
We started evaluating <a href="https://garnix.io/">Garnix</a> as a replacement for our
<a href="https://github.com/NixOS/hydra">Hydra</a> CI system, and explored containerisation for our
<a href="https://aws.amazon.com/pm/lambda">Lambda</a>-based services using Nix’s
<a href="https://ryantm.github.io/nixpkgs/builders/images/dockertools/">dockerTools</a>, proving out a model
we’re looking to adopt more broadly in 2026.</p>
<p>At the risk of repeating myself - it is staggering to me that a team of this size can achieve so
much in a year. I feel very blessed to work with such a great crew.</p>
<h2 id="the-cost-of-doing-it-all-at-once">The cost of doing it all at once</h2>
<p>Reading the sections above, it would be easy to imagine these as neat, sequential projects handed
off between well-defined teams. The reality was that all of these projects were achieved with teams
working in parallel. We had developers rotating between projects every eight weeks based on
availability, timezone and skillset. Within a given cycle, developers were focused on a single
project - but at each rotation boundary they needed to ramp up on an entirely different system,
understand the business context behind the work and build enough mental model to be productive. Over
the course of the year, that ramp-up cost compounded.</p>
<p>Coordinating all of this fell disproportionately on a small number of people. The LobeChat rollout,
for example, was happening alongside everything else - the roadshows, the support queries, the
software updates - and was largely driven by one person (ahem) who was also trying to stay across
every other active workstream. We needed to make sure that changes to shared infrastructure didn’t
block an in-flight feature (and of course they did, multiple times), that deployments didn’t
collide (ditto) and that the groups picking up a piece of work at the start of each cycle had enough
context to be effective without lengthy handovers (I’ll give us a solid B+ on this).</p>
<p>By the second half of the year the cumulative weight of all these rotations was something the whole
team felt. We got better at writing things down and leaving breadcrumbs for the next group, but the
cognitive overhead of supporting this many concurrent workstreams across a team of our size was
significant.</p>
<h2 id="looking-ahead">Looking ahead</h2>
<p>Towards the end of the year one of the team leads suggested we read the <a href="https://teamtopologies.com/book">Team Topologies book</a>.
After reading the book and having many long discussions, we made the decision to start 2026
with a new team structure, categorising our codebases and configuration sets into a
“Customer Journey” business stream, an “Operations” business stream or an “Infrastructure” platform
team. When we presented this to the team as a potential change, there was a palpable sense of relief
and enthusiasm.</p>
<p>This comes at a good time - 2026 is shaping up to be an exciting year for Bellroy, with a fantastic
team and an expansion of where we appear in the world and what we’re bringing to our customers.
We’ve already seen some benefits with the change to the new structure: better
relationships with stakeholders and our developers reporting a much less tumultuous project
transition experience. But as with any change, there are trade-offs: our AI tooling and strategy
still has no clear owner other than our CTO and there is a stream of work around developer tooling
that we don’t currently have the resources to get done. We need to consider further hiring to be
able to properly service these work streams.</p>
<p>Who knows, this time next year we might have <em>16</em> people!</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Fri, 13 Mar 2026 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/reflections-on-bellroys-tech-team-2025.html</guid>
    <dc:creator>Mike Webb</dc:creator>
</item>
<item>
    <title>Some Haskell idioms we like</title>
    <link>https://exploring-better-ways.bellroy.com/some-haskell-idioms-we-like.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Some Haskell idioms we like</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/jack.png"
              alt="Jack Kelly profile image"
            />
            <a class="no-underline font-bold" href="/contributors/jack_kelly.html">Jack Kelly</a>
            <small>2026-01-14</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>Scaling up our Haskell usage at Bellroy has meant <a href="https://www.youtube.com/watch?v=L4h6VegK1BI">cultivating a
“house style” or “engineering dialect”</a>.
While a house style obviously means making decisions about the
traditional Haskell decision points — choosing a preferred set of
libraries and language extensions — we also believe that a good
engineering dialect means forming opinions about the way we express
code. Haskell is <a href="https://www.youtube.com/watch?v=fty9QL4aSRc">a fairly simple language under all the
syntax</a>;
the same idea can have many different expressions, some much clearer
than others. In this post, we’ll share a few small idioms that we’ve
adopted. They might not all be novel, but we think they’re valuable
enough to document.</p>
<h2 id="explicit-construction-in-concrete-monads">Explicit construction in concrete <code>Monad</code>s</h2>
<p><strong>Idiom:</strong> When working in a concrete monad like <code>Maybe</code>, <code>Either e</code>
or <code>[]</code>, use that type’s data constructors explicitly instead of
calling <code>pure</code>.</p>
<p><strong>Example:</strong></p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- This is a simplified example from one of our internal DSLs.</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- | AST for terms.</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Term</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>  <span class="ot">=</span> <span class="dt">TmBool</span> <span class="dt">Bool</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> <span class="dt">TmNumber</span> <span class="dt">Rational</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>  <span class="op">|</span> <span class="dt">TmAnd</span> <span class="dt">Term</span> <span class="dt">Term</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- Plus some other constructors</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- | Enum of types.</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Type</span> <span class="ot">=</span> <span class="dt">TyBool</span> <span class="op">|</span> <span class="dt">TyNumber</span> <span class="co">-- Plus some others</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">TypedTerm</span> <span class="ot">=</span> <span class="dt">TypedTerm</span> <span class="dt">Type</span> <span class="dt">Term</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="co">-- | Type-check a term.</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="co">-- If an expected type is provided, perform checking; if not</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a><span class="co">-- attempt to perform inference.</span></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="ot">infer ::</span> <span class="dt">Maybe</span> <span class="dt">Type</span> <span class="ot">-&gt;</span> <span class="dt">Term</span> <span class="ot">-&gt;</span> <span class="dt">Either</span> <span class="dt">TypeError</span> <span class="dt">TypedTerm</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>infer expectedTy term <span class="ot">=</span> <span class="kw">case</span> term <span class="kw">of</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>  <span class="dt">TmBool</span> _ <span class="ot">-&gt;</span> <span class="dt">Right</span> <span class="op">$</span> <span class="dt">TypedTerm</span> <span class="dt">TyBool</span> term</span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>  <span class="dt">TmNumber</span> _ <span class="ot">-&gt;</span> <span class="dt">Right</span> <span class="op">$</span> <span class="dt">TypedTerm</span> <span class="dt">TyNumber</span> term</span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a>  <span class="dt">TmAnd</span> l r <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a>    <span class="dt">TypedTerm</span> <span class="dt">TyBool</span> _ <span class="ot">&lt;-</span> infer (<span class="dt">Just</span> <span class="dt">TyBool</span>) l</span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a>    <span class="dt">TypedTerm</span> <span class="dt">TyBool</span> _ <span class="ot">&lt;-</span> infer (<span class="dt">Just</span> <span class="dt">TyBool</span>) r</span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Right</span> <span class="op">$</span> <span class="dt">TypedTerm</span> <span class="dt">TyBool</span> term</span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a><span class="co">--  ^^^^^</span></span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a><span class="co">--  We use an explicit `Right` here to remind the reader that this</span></span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a><span class="co">--  `do`-block only assembles data, and does not perform side effects.</span></span></code></pre></div>
<p><strong>Discussion:</strong> The primary use of Haskell’s <code>Monad</code> typeclass is to
allow sequential description of operations “in a context” using <code>do</code>
blocks. Although Haskell teaching material often demonstrates <code>Monad</code>
instances for many data types before teaching <code>IO</code>, it is typically
the “stateful” monads like <code>State</code> or <code>IO</code> that have complex enough
control flow to warrant <code>do</code> blocks. This creates an implicit
association in some readers’ minds between <code>do</code> blocks and sequential,
side-effectful code, and most of our <code>do</code> blocks are of this
type. Writing out the “success constructor” explicitly (e.g. <code>Just</code>,
<code>Right</code>) indicates when this is not the case, and shows that no side
effects are happening here. It is a small difference, but a clarifying
habit, especially when there’s an outer <code>do</code>-block in an <code>IO</code>-like
monad and a <code>Maybe</code> or <code>Either</code> value being constructed in a <code>let</code> or
in a function argument.</p>
<h2 id="design-modules-for-qualified-import">Design modules for qualified import</h2>
<p><strong>Idiom:</strong> Choose names that read well when their containing module is
imported <code>qualified</code>. Avoid contorting identifier names to
disambiguate them from similar names in other modules.</p>
<p><strong>Examples:</strong></p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">Servant.Client.Handle</span> <span class="kw">where</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- | We say @Handle@ and not something like @ServantHandle@, so</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- that code which uses one handle can be kept more compact.</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- Code which needs to disambiguate between handles can consistently</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- refer to them as (e.g.) @Servant.Client.Handle@ or @DynamoDB.Handle@.</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Handle</span> m <span class="ot">=</span> <span class="dt">Handle</span> {<span class="op">..</span>}</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="ot">addCaching ::</span> <span class="dt">CacheConfig</span> <span class="ot">-&gt;</span> <span class="dt">Handle</span> m <span class="ot">-&gt;</span> <span class="dt">Handle</span> m</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>addCaching <span class="ot">=</span> <span class="fu">undefined</span></span></code></pre></div>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- | Sometimes moving an enum into its own module can help.</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- Here, we can @import qualified Bellroy.Shipping.Speed as Speed@</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- and then write (e.g.) @Speed.Regular@.</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">Bellroy.Shipping.Speed</span> (<span class="dt">Speed</span>(<span class="op">..</span>)) <span class="kw">where</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Speed</span> <span class="ot">=</span> <span class="dt">Regular</span> <span class="op">|</span> <span class="dt">Expedited</span> <span class="op">|</span> <span class="dt">Overnight</span></span></code></pre></div>
<p><strong>Discussion:</strong> As the codebase grows, it becomes almost inevitable
that any given module will need to be imported <code>qualified</code> at some
point. Attempts to work around this by adding context to identifier
names (e.g. <code>data ServantHandle m = ...</code>, <code>cacheServantHandle</code>) create
awkward identifiers like <code>Servant.cacheServantHandle</code> when a
<code>qualified</code> import inevitably becomes necessary. Often the qualified
import ends up being the same or similar length as a “contorted” name
while reading more cleanly.</p>
<p>This idiom works best when applied to modules containing a primary
type (such as a data structure or “handle”) and a collection of
functions that operate mostly on that type.</p>
<h2 id="unpack-large-patterns-using-a-where-clause">Unpack large patterns using a <code>where</code> clause</h2>
<p><strong>Idiom:</strong> Keep function parameter declarations compact by unpacking
larger patterns inside a <code>where</code> clause. This can also be done using
<code>let</code>.</p>
<p><strong>Examples:</strong></p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- When large patterns necessitate breaking the LHS of the `=`</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- over multiple lines, it&#39;s difficult to see where the body begins.</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>someFunction</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>  (<span class="dt">SomeRecord</span> {field1, field2, field3})</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>  anArg</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>  someOtherArg<span class="op">@</span>(<span class="dt">MorePatternNoise</span> {<span class="op">..</span>}) <span class="ot">=</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>    body</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- Instead, move the pattern matches down into a `where`:</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>someFunction&#39; someRecord anArg someOtherArg <span class="ot">=</span> body</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>    <span class="dt">SomeRecord</span> {field1, field2, field3} <span class="ot">=</span> someRecord</span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>    <span class="dt">MorePatternNoise</span> {<span class="op">..</span>} <span class="ot">=</span> someOtherArg</span></code></pre></div>
<p>Another (simplified) example from real code:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ot">shipTo ::</span> <span class="dt">SalesOrder</span> <span class="ot">-&gt;</span> <span class="dt">Either</span> <span class="dt">Error</span> <span class="dt">Logistics.Import.ShipTo</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>shipTo <span class="dt">SalesOrder</span>{<span class="op">..</span>} <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- Omitted: a bunch of code that parses and massages</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- data into the form which downstream wants.</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Right</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Logistics.Import.ShipTo</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>      { name <span class="ot">=</span> fromMaybe fullName&#39; orgName,</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>        attention <span class="ot">=</span> fullName&#39; <span class="op">&lt;$</span> orgName,</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>        address1,</span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>        address2,</span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>        address3,</span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>        city <span class="ot">=</span> city&#39;,</span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>        postalCode <span class="ot">=</span> postalCode&#39;,</span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>        country <span class="ot">=</span> country&#39;,</span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>        phone,</span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>        email,</span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>        consigneeTaxId <span class="ot">=</span> <span class="dt">Nothing</span></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a>      }</span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Address</span> {city, country, postalCode, stateCode, stateName} <span class="ot">=</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>      applyShippingAddressOverrides customerShippingAddress</span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Attention</span> {fullName, phone, email} <span class="ot">=</span> attention</span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a> <span class="co">-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span></span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a> <span class="co">-- Instead of bulking out the pattern-match on `SalesOrder{..}`,</span></span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a> <span class="co">-- we match `Attention` here. This makes it much easier to skim</span></span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a> <span class="co">-- the parameter declaration of a function and read through its body.</span></span></code></pre></div>
<h2 id="use-inversemap-liberally">Use <code>inverseMap</code> liberally</h2>
<p><strong>Idiom:</strong> When writing paired injection/lookup functions (print/parse
is our most common example), use
<a href="https://hackage-content.haskell.org/package/relude"><code>relude</code></a>’s
<code>inverseMap</code> function (or an equivalent) to derive the “lookup” side
from the “injection side”. The
<a href="https://hackage-content.haskell.org/package/relude-1.2.2.2/docs/Relude-Enum.html#v:inverseMap"><code>inverseMap</code></a>
function computes the partial inverse of the input function by
enumerating its domain:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ot">inverseMap ::</span> (<span class="dt">Bounded</span> a, <span class="dt">Enum</span> a, <span class="dt">Ord</span> k) <span class="ot">=&gt;</span> (a <span class="ot">-&gt;</span> k) <span class="ot">-&gt;</span> k <span class="ot">-&gt;</span> <span class="dt">Maybe</span> a</span></code></pre></div>
<p><strong>Example:</strong></p>
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">ErrorCode</span> <span class="ot">=</span> <span class="dt">InsufficientCredits</span> <span class="op">|</span> <span class="dt">InvalidParams</span> <span class="op">|</span> <span class="dt">General</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>  <span class="kw">deriving</span> stock (<span class="dt">Eq</span>, <span class="dt">Show</span>, <span class="dt">Generic</span>, <span class="dt">Enum</span>, <span class="dt">Bounded</span>)</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="ot">renderErrorCode ::</span> <span class="dt">ErrorCode</span> <span class="ot">-&gt;</span> <span class="dt">Text</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>renderErrorCode <span class="ot">=</span> \<span class="kw">case</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">InsufficientCredits</span> <span class="ot">-&gt;</span> <span class="st">&quot;insufficient_credits&quot;</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">InvalidParams</span> <span class="ot">-&gt;</span> <span class="st">&quot;invalid_params&quot;</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">General</span> <span class="ot">-&gt;</span> <span class="st">&quot;general&quot;</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a><span class="ot">parseErrorCode ::</span> <span class="dt">Text</span> <span class="ot">-&gt;</span> <span class="dt">Maybe</span> <span class="dt">ErrorCode</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a>parseErrorCode <span class="ot">=</span> inverseMap renderErrorCode</span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a>              <span class="co">-- ^^^^^^^^^^</span></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a>              <span class="co">-- Avoids writing `parseErrorCode` by hand.</span></span></code></pre></div>
<p><strong>Discussion:</strong></p>
<p>A common cause of bugs in print/parse function pairs comes from adding
a new constructor to the data type and failing to update the
parser. Writing the parser using <code>inverseMap</code> makes this impossible,
because the parser is derived from the printer and GHC enforces the
completeness of the printer’s <code>case</code>-match.</p>
<p>According to Hoogle, <code>inverseMap</code> is only provided by the (excellent)
<code>relude</code> custom prelude, but is easy enough to define
independently. The only subtlety is ensuring that the intermediate map
is shared between calls:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Declare the `k` argument in a lambda so GHC thinks the function</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- is &quot;fully applied&quot; with one argument.</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- This could also use `enumerate :: (Bounded a, Enum a) =&gt; [a]`,</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- which arrives in base-4.22 (GHC 9.14).</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a><span class="ot">inverseMap ::</span></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a>  <span class="kw">forall</span> a k<span class="op">.</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a>  (<span class="dt">Bounded</span> a, <span class="dt">Enum</span> a, <span class="dt">Ord</span> k) <span class="ot">=&gt;</span> (a <span class="ot">-&gt;</span> k) <span class="ot">-&gt;</span> k <span class="ot">-&gt;</span> <span class="dt">Maybe</span> a</span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a>inverseMap f <span class="ot">=</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a>  <span class="kw">let</span> m <span class="ot">=</span> Map.fromList [(f a, a) <span class="op">|</span> a <span class="ot">&lt;-</span> [<span class="fu">minBound</span> <span class="op">..</span> <span class="fu">maxBound</span>]<span class="ot"> ::</span> [a]]</span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a>   <span class="kw">in</span> \k <span class="ot">-&gt;</span> Map.lookup k m</span></code></pre></div>
<h2 id="takeaways">Takeaways</h2>
<p>Haskell blogs often use the language’s expressive type system and
other headline features to demonstrate some very spectacular things,
but there’s also a lot of expressivity in its more mundane features
and through careful choices of names. Idioms like the ones we’ve
shared here tend not to get the blog post treatment, but instead
percolate through less formal channels like code reviews, chat and
forum posts. We felt it was worth surfacing some of the ones we’ve
found, because they add clarity without adding much code, and don’t
impose limits on the language features we use (although we have an
evolving conversation about that too). We hope you also find them
useful.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Wed, 14 Jan 2026 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/some-haskell-idioms-we-like.html</guid>
    <dc:creator>Jack Kelly</dc:creator>
</item>
<item>
    <title>Free applicatives, the handle pattern, and remote systems</title>
    <link>https://exploring-better-ways.bellroy.com/free-applicatives-the-handle-pattern-and-remote-systems.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Free applicatives, the handle pattern, and remote systems</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/jack.png"
              alt="Jack Kelly profile image"
            />
            <a class="no-underline font-bold" href="/contributors/jack_kelly.html">Jack Kelly</a>
            <small>2025-10-13</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>We recently refactored some gnarly code that manipulated customer and
order records in our enterprise resource planning (ERP) system. That
system had a few idiosyncrasies which complicated this task:</p>
<ul>
<li>Creating new records required referencing other entities by internal
ID, so we had to do a number of lookups before issuing “create
record” requests;</li>
<li>For some entity types, we found it easiest to issue “search” API
calls and extract the required IDs from the returned search
results. This necessitated an extra parsing step between “we have a
successful response” and “we have the ID we’re looking for”; and</li>
<li>Requests are often slow, but the marginal cost of additional
requests in a batch was quite low. This meant that we could expect
some good results from batching related requests together.</li>
</ul>
<p>The benefits of batching led us to seek a solution that permitted
static analysis. Applicative functors have a completely static control
flow, and cannot express computations where one step depends on the
result of a previous step. A well-chosen applicative would let us
analyse the requests we need to send without executing any of them,
batch queries together without worrying about data dependencies, and
then route relevant results to each individual query to parse (if
necessary). Our library users could ignore batching details but still
gain the efficiency benefits of a batch query API.</p>
<p>In this post, we’ll look at how we’ve been using handles, what “free
structures” are, how free applicatives elegantly solved some of our
problems interfacing with a remote system, and how they interacted
especially well with the “handle pattern”.</p>
<h2 id="handles-as-bellroy-uses-them">Handles, as Bellroy uses them</h2>
<p>The “<a href="https://jaspervdj.be/posts/2018-03-08-handle-pattern.html">handle pattern</a>”
is a Haskell idiom that is similar to dependency injection in
mainstream languages. Instead of directly writing in the side effects
we want our code to perform, we accept a record of functions that we
call a “handle”. (In an object-oriented language, we’d probably accept
an object that implements an abstract interface instead of a record.)
These handles can group related functions into a single record but
often only contain one:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Handle</span> e m <span class="ot">=</span> <span class="dt">Handle</span> {</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ot">    performRequest ::</span> <span class="dt">ERP.Request</span> <span class="ot">-&gt;</span> m (<span class="dt">Either</span> e <span class="dt">Aeson.Value</span>)</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  }</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- Plus some other handle-making functions e.g. for testing.</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="ot">newHandle ::</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">MonadIO</span> m <span class="ot">=&gt;</span> <span class="dt">ERP.Credentials</span> <span class="ot">-&gt;</span> m (<span class="dt">Handle</span> <span class="dt">ERP.Error</span> m)</span></code></pre></div>
<p>Functions that consume handles generally look like this:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">someFunction ::</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- When all side effects come from handles,</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- we rarely need anything stronger than `Monad`.</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Monad</span> m <span class="ot">=&gt;</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- First: Any handles we need</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">FooHandle</span> m <span class="ot">-&gt;</span> <span class="dt">BarHandle</span> m <span class="ot">-&gt;</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- Second: Other &quot;normal&quot; arguments</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Argument1</span> <span class="ot">-&gt;</span> <span class="op">..</span> <span class="ot">-&gt;</span> <span class="dt">ArgumentN</span> <span class="ot">-&gt;</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>  m <span class="dt">Result</span></span></code></pre></div>
<p>This idiom is a simpler, library-free alternative to effect system
libraries like
<a href="https://hackage.haskell.org/package/effectful"><code>effectful</code></a>,
<a href="https://hackage.haskell.org/package/bluefin"><code>bluefin</code></a>,
<a href="https://hackage.haskell.org/package/heftia"><code>heftia</code></a> and
<a href="https://hackage.haskell.org/package/polysemy"><code>polysemy</code></a>. We
previously wrote about an
<a href="https://exploring-better-ways.bellroy.com/integrating-effectful-and-persistent.html">experiment with <code>effectful</code></a>,
but we have still not committed to an effect system. Instead, we are
refactoring towards handles as a way to encapsulate our side effects,
and because it should be easy to convert handle-using code to an
effect system if and when we choose one.</p>
<p>Because we have code written against other idioms (e.g. MTL-style
classes), and because we often find it convenient to introduce an
<code>ExceptT e</code> or <code>MaybeT</code> monad transformer in the body of our
functions, we sometimes need to change the monad of a handle that
we’ve been given. We do this by providing a <code>hoistHandle</code> function:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ot">hoistHandle ::</span> (<span class="kw">forall</span> x <span class="op">.</span> f x <span class="ot">-&gt;</span> g x) <span class="ot">-&gt;</span> <span class="dt">Handle</span> f <span class="ot">-&gt;</span> <span class="dt">Handle</span> g</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>hoistHandle f <span class="dt">Handle</span> {performRequest} <span class="ot">=</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Handle</span> {performRequest <span class="ot">=</span> f <span class="op">.</span> performRequest}</span></code></pre></div>
<p>That first argument, <code>forall x . f x -&gt; g x</code>, is worth commenting
on. A <code>forall</code> in a type signature explicitly introduces a type
variable that is provided by the function’s caller. For a simpler
example of how <code>forall</code> works here, let’s look at the <code>map</code> function
on lists, but with explicit <code>forall</code>s:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="fu">map</span><span class="ot"> ::</span> <span class="kw">forall</span> a b <span class="op">.</span> (a <span class="ot">-&gt;</span> b) <span class="ot">-&gt;</span> [a] <span class="ot">-&gt;</span> [b]</span></code></pre></div>
<p>The caller of <code>map</code> gets to choose the types of <code>a</code> and <code>b</code>, and GHC
is often smart enough to figure this out automatically:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- GHC concludes that it needs to call</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- `map` with `Int` for `a` and `String` for `b`.</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>ghci<span class="op">&gt;</span> <span class="fu">map</span> <span class="fu">show</span> [<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">3</span>]</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>[<span class="st">&quot;1&quot;</span>,<span class="st">&quot;2&quot;</span>,<span class="st">&quot;3&quot;</span>]</span></code></pre></div>
<p>In our <code>hoistHandle</code> function, we let the <strong>caller</strong> choose <code>f</code> and
<code>g</code>, but they must provide us a function where <strong>we</strong> are allowed to
choose <code>x</code>. The types force this function to convert <code>f x</code> into <code>g x</code>
in a way that’s blind to what <code>x</code> actually is — guaranteeing that
the conversion only changes structure, not wrapped values. It also
ensures that we can write <code>hoistHandle</code> for a handle containing
multiple functions, because we can choose a different <code>x</code> for each
one.</p>
<h2 id="building-our-applicative">Building our applicative</h2>
<p>We want to build a structure that is essentially a syntax tree of the
operations we want to perform. This means it needs to hold the
requests we want to send, and because we want it to be an applicative,
we’ll add constructors to represent <code>pure</code> and <code>(&lt;*&gt;)</code>:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Query</span> a <span class="kw">where</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">QueryAndParse</span> <span class="ot">::</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>    <span class="dt">FromJSON</span> x <span class="ot">=&gt;</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>    <span class="dt">ERP.Request</span> <span class="ot">-&gt;</span> (x <span class="ot">-&gt;</span> <span class="dt">Either</span> <span class="dt">Text</span> a) <span class="ot">-&gt;</span> <span class="dt">Query</span> a</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- Extra constructors to hold applicative structure</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Pure</span><span class="ot"> ::</span> a <span class="ot">-&gt;</span> <span class="dt">Query</span> a</span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Ap</span><span class="ot"> ::</span> <span class="dt">Query</span> (a <span class="ot">-&gt;</span> b) <span class="ot">-&gt;</span> <span class="dt">Query</span> a <span class="ot">-&gt;</span> <span class="dt">Query</span> b</span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> stock <span class="kw">instance</span> <span class="dt">Functor</span> <span class="dt">Query</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Applicative</span> <span class="dt">Query</span> <span class="kw">where</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a>  <span class="fu">pure</span> <span class="ot">=</span> <span class="dt">Pure</span></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Pure</span> f <span class="op">&lt;*&gt;</span> <span class="dt">Pure</span> x <span class="ot">=</span> <span class="dt">Pure</span> <span class="op">$</span> f x</span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a>  <span class="dt">QueryAndParse</span> req f <span class="op">&lt;*&gt;</span> <span class="dt">Pure</span> a <span class="ot">=</span></span>
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a>    <span class="dt">QueryAndParse</span> req <span class="op">$</span> <span class="fu">fmap</span> (<span class="op">$</span> a) <span class="op">.</span> f</span>
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- Plus another seven cases, being careful that</span></span>
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- each case obeys the applicative laws.</span></span></code></pre></div>
<p><code>QueryAndParse</code> is the only data constructor directly relevant to our
problem. It captures the request we want to make against the ERP, a
<code>FromJSON x</code> constraint so we can parse the raw response into some
intermediate type representing an API response, and a function <code>x -&gt; Either Text a</code> to extract just the data we want from that API
response.</p>
<p>This design could work, but it’s a fair amount of boilerplate, and the
next time we want an applicative like this we’d need to repeat most of
it. In the next section, we’ll use a free applicative to separate the
general “applicative” code from the specific “query and parse” code.</p>
<h2 id="what-is-a-free-structure">What is a “free structure”?</h2>
<p>To understand how free applicatives help us with this problem, we need
to have some idea what “freeness” means in this context. The Haskell
community usually talks about taking “the free <code>$class</code> over <code>$type</code>”
as a way to make <code>$type</code> an instance of <code>$class</code>, by adding <em>just
enough</em> structure to construct a lawful instance of <code>$class</code>. Packages
like <a href="https://hackage.haskell.org/package/free"><code>free</code></a> provide
wrapping types that hold values of <code>$type</code> and provide instances of
<code>$class</code>.</p>
<p>Let’s pare our <code>Query</code> type back to something much smaller: a type
representing a single request against our ERP:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">OneQuery</span> a <span class="kw">where</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">QueryAndParse</span> <span class="ot">::</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>    <span class="dt">FromJSON</span> x <span class="ot">=&gt;</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>    <span class="dt">ERP.Request</span> <span class="ot">-&gt;</span> (x <span class="ot">-&gt;</span> <span class="dt">Either</span> <span class="dt">Text</span> a) <span class="ot">-&gt;</span> <span class="dt">OneQuery</span> a</span></code></pre></div>
<p>We will now re-write <code>Query</code> as the free <code>Applicative</code> over
<code>OneQuery</code>. To make <code>OneQuery</code> into an <code>Applicative</code>, we’ll use the
<code>Ap</code> wrapper from
<a href="https://hackage.haskell.org/package/free-5.2/docs/Control-Applicative-Free.html"><code>Control.Applicative.Free</code></a>.
Here is its interface:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- `Ap f` is the free applicative over `f`. We never use its</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- constructors directly; instead, we use `liftAp` and the</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- `Applicative` interface (`pure`, `(&lt;*&gt;)`, `liftA2`, etc.)</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Ap</span> f a</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- For *any* `f`, `Ap f` is an applicative.</span></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Applicative</span> (<span class="dt">Ap</span> f)</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- We can turn any `f a` into an `Ap f a`.</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a><span class="ot">liftAp ::</span> f a <span class="ot">-&gt;</span> <span class="dt">Ap</span> f a</span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a><span class="co">-- If we can turn our `f` into some applicative `g`, then we can turn</span></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a><span class="co">-- `Ap f a` into `g a` in a way that respects the Applicative laws:</span></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a><span class="co">-- runAp _ (pure x) = pure x</span></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a><span class="co">-- runAp f (x &lt;*&gt; y) = (runAp f x) &lt;*&gt; (runAp f y)</span></span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a><span class="co">-- Similar to the `forall x. f x -&gt; g x` in `hoistHandle` above,</span></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a><span class="co">-- this lets us turn each `f x` stored in the `Ap f a` into a</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a><span class="co">-- corresponding `g x`, while remaining ignorant of the specific</span></span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a><span class="co">-- type `x`.</span></span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a><span class="ot">runAp ::</span> <span class="dt">Applicative</span> g <span class="ot">=&gt;</span> (<span class="kw">forall</span> x<span class="op">.</span> f x <span class="ot">-&gt;</span> g x) <span class="ot">-&gt;</span> <span class="dt">Ap</span> f a <span class="ot">-&gt;</span> g a</span></code></pre></div>
<p>We’ll skip the implementations because we won’t ever manually recurse
through an <code>Ap f a</code> value; from a modularity perspective, we are only
interested in the abstract interface. We declare <code>Query</code> as the free
applicative over <code>OneQuery</code>, make it a <code>newtype</code> to establish an
abstraction boundary between the query library and its callers, and
use <code>deriving newtype</code> to avoid writing any applicative structure
ourselves:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">Query</span> a <span class="ot">=</span> <span class="dt">Query</span> (<span class="dt">Free.Ap</span> <span class="dt">OneQuery</span> a)</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>  <span class="kw">deriving</span> stock <span class="dt">Functor</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a>  <span class="kw">deriving</span> <span class="kw">newtype</span> <span class="dt">Applicative</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- Helper functions to avoid building `Query` values by hand.</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="ot">query ::</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a>  <span class="dt">FromJSON</span> a <span class="ot">=&gt;</span> <span class="dt">ERP.Request</span> <span class="ot">-&gt;</span> <span class="dt">Query</span> a</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a>query req <span class="ot">=</span></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Query</span> <span class="op">.</span> Free.liftAp <span class="op">$</span> <span class="dt">QueryAndParse</span> req <span class="dt">Right</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a><span class="ot">queryAndParse ::</span></span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a>  <span class="dt">FromJSON</span> a <span class="ot">=&gt;</span> <span class="dt">ERP.Request</span> <span class="ot">-&gt;</span> (a <span class="ot">-&gt;</span> <span class="dt">Either</span> <span class="dt">Text</span> b) <span class="ot">-&gt;</span> <span class="dt">Query</span> b</span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a>queryAndParse req f <span class="ot">=</span></span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Query</span> <span class="op">.</span> Free.liftAp <span class="op">$</span> <span class="dt">QueryAndParse</span> req f</span></code></pre></div>
<h2 id="building-a-query">Building a <code>Query</code></h2>
<p>From this infrastructure, we can write functions representing
individual queries. These are direct applications of the <code>query</code> and
<code>queryAndParse</code> helpers:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ot">queryLocationId ::</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">ERP.Location.Name</span> <span class="ot">-&gt;</span> <span class="dt">Query</span> <span class="dt">ERP.Location.Id</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a>queryLocationId locationName <span class="ot">=</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a>  query <span class="op">$</span> ERP.lookupLocation locationName</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="ot">queryOrderId ::</span></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">ERP.Order.Name</span> <span class="ot">-&gt;</span> <span class="dt">Query</span> <span class="dt">ERP.Order.Id</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a>queryOrderId orderName <span class="ot">=</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a>  queryAndParse</span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a>    (ERP.searchOrders orderName) <span class="op">$</span> \<span class="kw">case</span> <span class="ot">-&gt;</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a>      [order] <span class="ot">-&gt;</span> <span class="dt">Right</span> order</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a>      (_<span class="op">:</span>_) <span class="ot">-&gt;</span> <span class="dt">Left</span> <span class="st">&quot;Multiple Orders in response&quot;</span></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a>      [] <span class="ot">-&gt;</span> <span class="dt">Left</span> <span class="st">&quot;No Orders in response&quot;</span></span></code></pre></div>
<p>From these functions we can build up complex queries using applicative
operations:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ot">queryOrderAndLocation ::</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">ERP.Order.Name</span> <span class="ot">-&gt;</span> <span class="dt">ERP.Location.Name</span> <span class="ot">-&gt;</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Query</span> (<span class="dt">ERP.Order.Id</span>, <span class="dt">ERP.Location.Id</span>)</span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a>queryOrderAndLocation orderName locationName <span class="ot">=</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a>  liftA2 (,) (queryOrderId orderName) (queryLocationId locationName)</span></code></pre></div>
<h2 id="running-a-query">Running a <code>Query</code></h2>
<p>We can run a <code>Query</code> by using
<a href="https://hackage.haskell.org/package/free-5.2/docs/Control-Applicative-Free.html#v:runAp"><code>runAp</code></a>.
Because we’re in an applicative context and we’re making requests that
don’t alter the remote system, we can run every request and use a
<a href="https://hackage.haskell.org/package/validation-selective"><code>Validation</code></a>
to collect all failures:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">RunQueryError</span> e <span class="ot">=</span> <span class="dt">RequestError</span> e <span class="op">|</span> <span class="dt">JsonError</span> <span class="dt">Text</span> <span class="op">|</span> <span class="dt">ParseResultError</span> <span class="dt">Text</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">RunQueryErrors</span> e <span class="ot">=</span> <span class="dt">NonEmpty</span> (<span class="dt">RunQueryError</span> e)</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a><span class="ot">runQuery ::</span> <span class="kw">forall</span> e m a<span class="op">.</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Monad</span> m <span class="ot">=&gt;</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">ERP.Handle</span> e m <span class="ot">-&gt;</span></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Query</span> a <span class="ot">-&gt;</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a>  m (<span class="dt">Validation</span> (<span class="dt">RunQueryErrors</span> e) a)</span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a>runQuery <span class="dt">ERP.Handle</span>{performRequest} (<span class="dt">Query</span> q) <span class="ot">=</span></span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a>  getCompose <span class="op">$</span> Free.runAp (<span class="dt">Compose</span> <span class="op">.</span> go) q</span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a><span class="ot">    go ::</span> <span class="dt">OneQuery</span> x <span class="ot">-&gt;</span> m (<span class="dt">Validation</span> (<span class="dt">RunQueryErrors</span> e) x)</span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a>    go (<span class="dt">QueryAndParse</span> req parse) <span class="ot">=</span> performRequest req <span class="op">&lt;&amp;&gt;</span> \<span class="kw">case</span></span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a>      <span class="dt">Left</span> reqErr <span class="ot">-&gt;</span> <span class="dt">Failure</span> <span class="op">.</span> NonEmpty.singleton <span class="op">$</span> <span class="dt">RequestError</span> reqErr</span>
<span id="cb12-15"><a href="#cb12-15" aria-hidden="true" tabindex="-1"></a>      <span class="dt">Right</span> value <span class="ot">-&gt;</span> <span class="kw">case</span> Aeson.parseEither Aeson.parseJSON value <span class="kw">of</span></span>
<span id="cb12-16"><a href="#cb12-16" aria-hidden="true" tabindex="-1"></a>        <span class="dt">Left</span> jsonErr <span class="ot">-&gt;</span> <span class="dt">Failure</span> <span class="op">.</span> NonEmpty.singleton <span class="op">.</span> <span class="dt">JsonError</span> <span class="op">$</span> Text.pack jsonErr</span>
<span id="cb12-17"><a href="#cb12-17" aria-hidden="true" tabindex="-1"></a>        <span class="dt">Right</span> x <span class="ot">-&gt;</span> <span class="kw">case</span> parse x <span class="kw">of</span></span>
<span id="cb12-18"><a href="#cb12-18" aria-hidden="true" tabindex="-1"></a>          <span class="dt">Left</span> parseErr <span class="ot">-&gt;</span> <span class="dt">Failure</span> <span class="op">.</span> NonEmpty.singleton <span class="op">$</span> <span class="dt">ParseResultError</span> parseErr</span>
<span id="cb12-19"><a href="#cb12-19" aria-hidden="true" tabindex="-1"></a>          <span class="dt">Right</span> a <span class="ot">-&gt;</span> <span class="dt">Success</span> a</span></code></pre></div>
<p>The implementation can be mostly derived by following the types, but
we’ll highlight some specifics:</p>
<ul>
<li><p><code>Validation e a</code> is a type that’s structurally isomorphic to
<code>Either e a</code>, but provides an <code>Applicative</code> instance that
accumulates errors:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- From the validation-selective package.</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Semigroup</span> e <span class="ot">=&gt;</span> <span class="dt">Applicative</span> (<span class="dt">Validation</span> e) <span class="kw">where</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a>  <span class="fu">pure</span> <span class="ot">=</span> <span class="dt">Success</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- This asymmetric way of writing &lt;*&gt; maximises laziness.</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Failure</span> e1 <span class="op">&lt;*&gt;</span> b <span class="ot">=</span> <span class="dt">Failure</span> <span class="op">$</span> <span class="kw">case</span> b <span class="kw">of</span></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Failure</span> e2 <span class="ot">-&gt;</span> e1 <span class="op">&lt;&gt;</span> e2</span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a>    <span class="dt">Success</span> _  <span class="ot">-&gt;</span> e1</span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Success</span> _ <span class="op">&lt;*&gt;</span> <span class="dt">Failure</span> e <span class="ot">=</span> <span class="dt">Failure</span> e</span>
<span id="cb13-10"><a href="#cb13-10" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Success</span> f <span class="op">&lt;*&gt;</span> <span class="dt">Success</span> a <span class="ot">=</span> <span class="dt">Success</span> (f a)</span></code></pre></div></li>
<li><p>Since the composition of any two applicatives is itself an
applicative,
<a href="https://hackage.haskell.org/package/base-4.21.0.0/docs/Data-Functor-Compose.html#t:Compose"><code>Data.Functor.Compose</code></a>
lets us combine the <code>m</code> and <code>Validation e</code> applicatives into
<code>Compose m (Validation e)</code>, which executes actions in <code>m</code> and
accumulates errors — exactly what we want.</p></li>
<li><p>Since we use the <code>Compose</code> constructor to wrap the result of <code>go</code>,
<code>Free.runAp</code> will return a <code>Compose m (Validation e) a</code> which we
must unwrap with <code>getCompose</code>.</p></li>
<li><p>The <code>go</code> function processes a single request held in a <code>OneQuery x</code>,
and <code>Free.runAp</code> uses it to build up the applicative combination of
each result.</p></li>
<li><p>We accept a handle telling us how to contact the ERP. This is the
key location where the handle pattern and the free applicative
interact, giving the library user a lot of power: the handle
parameter frees us from being coupled to any particular monad and
makes it easier to write tests for this code. We’ll see another way
to construct a <code>ERP.Handle</code> very soon.</p></li>
<li><p>The caller of the <code>Query</code> interface has no idea that we’re building
and consuming free structures under the hood. It’s an implementation
detail that doesn’t distort the abstraction boundary at all.</p></li>
</ul>
<h2 id="extracting-requests">Extracting requests</h2>
<p>Now that we can execute queries, let’s explore the main benefit of
free applicatives: the ability to analyse the applicative program
without running it. We can extract a monoidal summary of any free
applicative’s structure via
<a href="https://hackage.haskell.org/package/free-5.2/docs/Control-Applicative-Free.html#v:runAp_"><code>runAp_</code></a>:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="ot">runAp_ ::</span> <span class="dt">Monoid</span> m <span class="ot">=&gt;</span> (<span class="kw">forall</span> x <span class="op">.</span> f x <span class="ot">-&gt;</span> m) <span class="ot">-&gt;</span> <span class="dt">Ap</span> f a <span class="ot">-&gt;</span> m</span></code></pre></div>
<p>For an intuition why this is true, consider that the constant functor
<a href="https://hackage.haskell.org/package/base-4.21.0.0/docs/Data-Functor-Const.html#t:Const"><code>Const r</code></a>
has an <code>Applicative</code> instance whenever <code>r</code> is a monoid, because <code>pure</code>
stores a <code>mempty</code> value and <code>(&lt;*&gt;)</code> combines the held values with
<code>(&lt;&gt;)</code>. For a fun exercise, implement <code>runAp_</code> in terms of <code>runAp</code> and
<code>Const</code>.</p>
<p>We can use <code>runAp_</code> to extract a list of every request a <code>Query a</code>
will send:</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="ot">allRequests ::</span> <span class="dt">Query</span> a <span class="ot">-&gt;</span> [<span class="dt">Request</span>]</span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a>allRequests (<span class="dt">Query</span> q) <span class="ot">=</span> ordNub <span class="op">$</span> Free.runAp_ go q</span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true" tabindex="-1"></a><span class="ot">    go ::</span> <span class="dt">OneQuery</span> x <span class="ot">-&gt;</span> [<span class="dt">Request</span>]</span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true" tabindex="-1"></a>    go (<span class="dt">QueryAndParse</span> req _) <span class="ot">=</span> [req]</span></code></pre></div>
<p>Once we have the list of requests, we can look for ways to optimise
them. De-duplicating the requests with
<a href="https://hackage-content.haskell.org/package/relude-1.2.2.2/docs/Relude-Nub.html#v:ordNub"><code>ordNub</code></a>
is an easy optimisation, but if the remote API supports it, we could
do more advanced optimisations like using a batch request API.</p>
<p>As a simple demonstration, we can perform all the lookup requests in
advance and construct a <code>Map Request Aeson.Value</code>:</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="dt">SavedRequests</span> <span class="ot">=</span> <span class="dt">Map</span> <span class="dt">Request</span> <span class="dt">Aeson.Value</span></span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a><span class="ot">saveRequests ::</span></span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a>  <span class="kw">forall</span> e m<span class="op">.</span></span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Monad</span> m <span class="ot">=&gt;</span></span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Handle</span> e m <span class="ot">-&gt;</span> [<span class="dt">Request</span>] <span class="ot">-&gt;</span> m (<span class="dt">Either</span> e <span class="dt">SavedRequests</span>)</span>
<span id="cb16-7"><a href="#cb16-7" aria-hidden="true" tabindex="-1"></a>saveRequests <span class="dt">Handle</span>{performRequest} requests <span class="ot">=</span></span>
<span id="cb16-8"><a href="#cb16-8" aria-hidden="true" tabindex="-1"></a>  runExceptT <span class="op">$</span> Map.fromList <span class="op">&lt;$&gt;</span> <span class="fu">traverse</span> go requests</span>
<span id="cb16-9"><a href="#cb16-9" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb16-10"><a href="#cb16-10" aria-hidden="true" tabindex="-1"></a><span class="ot">    go ::</span> <span class="dt">Request</span> <span class="ot">-&gt;</span> <span class="dt">ExceptT</span> e m (<span class="dt">Request</span>, <span class="dt">Aeson.Value</span>)</span>
<span id="cb16-11"><a href="#cb16-11" aria-hidden="true" tabindex="-1"></a>    go req <span class="ot">=</span> (req,) <span class="op">&lt;$&gt;</span> <span class="dt">ExceptT</span> <span class="op">$</span> performRequest req</span></code></pre></div>
<p>Using a collection of saved results, we can construct a handle that
returns the saved responses instead of performing real requests:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="kw">newtype</span> <span class="dt">UnsavedRequestError</span> <span class="ot">=</span> <span class="dt">UnsavedRequestError</span> <span class="dt">Request</span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a><span class="ot">newHandleFromSavedRequests ::</span></span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a>  (<span class="dt">Applicative</span> m) <span class="ot">=&gt;</span> <span class="dt">SavedRequests</span> <span class="ot">-&gt;</span> <span class="dt">Handle</span> <span class="dt">UnsavedRequestError</span> m</span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a>newHandleFromSavedRequests requests <span class="ot">=</span></span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Handle</span></span>
<span id="cb17-7"><a href="#cb17-7" aria-hidden="true" tabindex="-1"></a>    { performRequest <span class="ot">=</span> \req <span class="ot">-&gt;</span></span>
<span id="cb17-8"><a href="#cb17-8" aria-hidden="true" tabindex="-1"></a>        <span class="fu">pure</span> <span class="op">.</span> <span class="fu">maybe</span> (<span class="dt">Left</span> (<span class="dt">UnsavedRequestError</span> req)) <span class="dt">Right</span> <span class="op">$</span></span>
<span id="cb17-9"><a href="#cb17-9" aria-hidden="true" tabindex="-1"></a>          Map.lookup req requests</span>
<span id="cb17-10"><a href="#cb17-10" aria-hidden="true" tabindex="-1"></a>    }</span></code></pre></div>
<p>This gives us a great story for testing. Since our <code>runQuery</code> works
with any handle, we can capture some real requests to a file, redact
any sensitive information, and create a pure handle built from saved
requests. We can then use this handle to write test cases that run
real code without performing side-effects.</p>
<p>If this example moved too quickly, or you want to see another
application of free structures, Justin Le has a spectacular post on
<a href="https://blog.jle.im/entry/free-alternative-regexp.html">matching regular expressions using the free <code>Alternative</code></a>.</p>
<h2 id="payoffs-and-limitations">Payoffs and limitations</h2>
<p>What have we achieved? We decided that we wanted an applicative to
describe queries against our remote system. Instead of inventing a
complicated data structure to represent the syntax tree of <code>pure</code> and
<code>(&lt;*&gt;)</code> calls, we defined a type just to hold one request and took the
free applicative over it. We also used the handle pattern to ask for
only the side-effects that we needed. Both patterns are reasonably
easy to implement, and in exchange we got some pretty neat benefits
that would’ve been harder to realise with either technique alone:</p>
<ol>
<li><p>We can analyse a <code>Query</code> without running it, and use the <code>Query</code> to
inform the handle we do eventually use;</p></li>
<li><p>As a special case of (1), library users can code against a
convenient interface and request individual records, but we can
inspect the set of queries before we begin execution and issue
optimised, parallelised, de-duplicated and batched requests in
their place;</p></li>
<li><p>We don’t have to abort at the first failed request — we can
collect and report <em>every</em> problem with a <code>Query</code>; and</p></li>
<li><p>We can record and replay requests, giving us a great testing story
in the style of Ruby’s <a href="https://github.com/vcr/vcr"><code>vcr</code></a> library.</p></li>
</ol>
<p>It’s not all roses, though. We lose a significant amount of expressive
power by giving up the monadic interface. For APIs where we need to
interleave pure queries and side-effecting requests, losing the
<code>Monad</code> instance might be a bridge too far. Chris Penner <a href="https://chrispenner.ca/posts/expressiveness-spectrum#closer-to-the-sweet-spot">suggests
that <code>Selective</code> functors could be closer to the sweet spot</a>,
but then you lose the nice ergonomics of <a href="https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/applicative_do.html#extension-ApplicativeDo"><code>-XApplicativeDo</code></a>.
Chris Done identifies an <a href="https://chrisdone.com/posts/applicative-wired-monad/">“Applicative-wired Monad” pattern</a>
which uses a monad only to plumb together applicative values.</p>
<p>So where does this leave us? The handle pattern has been working well
for us and we plan to continue refactoring code to use handles for the
foreseeable future. In narrow contexts where we want to take advantage
of static analysis, a well-chosen free applicative has given us a
surprising amount of modularity, testability and opportunities for
automatic optimisation. In the function that “runs” the free
applicative, these two idioms interacted in a very satisfying way: the
handle parameter gave us a lot of flexibility without asking library
users to write a lot of boilerplate.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Mon, 13 Oct 2025 12:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/free-applicatives-the-handle-pattern-and-remote-systems.html</guid>
    <dc:creator>Jack Kelly</dc:creator>
</item>
<item>
    <title>Designing for the different stages of a system's life</title>
    <link>https://exploring-better-ways.bellroy.com/designing-for-the-different-stages-of-a-systems-life.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Designing for the different stages of a system's life</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/mike_ai_avatar.jpeg"
              alt="Mike Webb profile image"
            />
            <a class="no-underline font-bold" href="/contributors/mike_webb.html">Mike Webb</a>
            <small>2025-07-22</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>Every software engineer has their war stories — those moments when a seemingly brilliant architectural
decision comes back to haunt them years later. Today, I want to share one of ours, because it
reveals something important about how we think about system design: we often make good architectural
choices for the things we <em>expect</em> to change but in ways that make it harder to adapt to the
unexpected.</p>
<h2 id="the-clean-architecture-revelation">The Clean Architecture revelation</h2>
<p>Like many engineering teams, we have a technical book club at Bellroy. Over the years, we’ve worked
through the classics that have shaped our industry — <a href="https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/">The Pragmatic Programmer</a>,
<a href="https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882">Clean Code</a>, and the
book that would prove both transformative and, eventually, problematic:
<a href="https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/">Clean Architecture</a>.</p>
<p>When we first read <a href="http://cleancoder.com/">Uncle Bob</a>’s treatise on architectural patterns, it felt
revelatory. Here was a framework that seemed to identify and solve many of the pain points our team
was experiencing. As a <a href="https://rubyonrails.org/">Ruby on Rails</a> shop experimenting with different
infrastructure providers and integration partners, we’d been bitten repeatedly by tight coupling.
Switching from one payment processor to another, or migrating from file-based storage to a database,
always seemed to require changes scattered throughout our codebase.</p>
<p>Clean Architecture promised a better way. The core principle is simple: dependencies should point
inward, toward your business logic, never outward toward implementation details. You achieve this
through interfaces and gateways — abstract boundaries that let your core domain remain blissfully
unaware of whether it’s talking to PostgreSQL or MongoDB, Stripe or PayPal.</p>
<p>We were sold. We printed out <a href="https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg">the famous Clean Architecture diagram</a>
and taped it to our office walls like an architectural commandment. We refactored our codebase to
embrace the pattern, creating gateway interfaces for external services and ensuring our business
logic lived in the pristine centre of our dependency circles.</p>
<p><img src="https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg" /></p>
<p>The benefits were immediate and tangible. Switching from file-based storage to a relational database
became trivial — we could just swap out the implementation behind the gateway interface. Our test
suite became more focused and faster, since we could easily mock external dependencies. We felt like
we’d discovered the secret to maintainable software.</p>
<h2 id="the-ultimate-test">The ultimate test</h2>
<p>It was around this time that we decided we needed more than an accounting system, we needed an
Enterprise Resource Planning (ERP) system.
ERP migrations are legendary for the chaos they unleash on businesses. Our accounting system, which
had served us well in our earlier days, had politely informed us that we’d grown too large for their
infrastructure. They suggested we might be happier elsewhere — a diplomatic way of saying we were
monopolizing their resources. We were suddenly on the clock.</p>
<p>A migration to an ERP would be the real test of Clean Architecture. We’d heard horror stories of
companies losing weeks of productivity, data inconsistencies causing financial headaches, and
integrations that limped along for months. We were determined to avoid that fate.</p>
<p>This is where Clean Architecture truly shone. Its gateway pattern gave us an elegant solution: we
modified our accounting gateway to write to <em>both</em> our incumbent system and the sandbox environment
of our new ERP. For three months, we ran this dual-write setup, comparing outputs at month-end and
methodically tracking down every discrepancy.</p>
<p>The switchover itself was almost anticlimactic. At midnight on June 30, 2019 — the end of the
Australian financial year — we deployed a single configuration change, updating our connection
string to point to the production ERP environment. We ran both systems in parallel for another month
as a safety net, then deployed one final change to stop writing to the old system entirely.</p>
<p>It was the smoothest integration I’ve ever been part of. We celebrated accordingly, convinced we’d
mastered the art of system evolution.</p>
<h2 id="when-architecture-becomes-archaeology">When architecture becomes archaeology</h2>
<p>But software has a way of humbling us, and our celebration proved premature. Not long after our ERP
success, we made another significant decision: we committed to functional programming and began
transitioning our entire codebase from <a href="https://www.ruby-lang.org/">Ruby</a> to <a href="https://www.haskell.org/">Haskell</a>.</p>
<p>We adopted a pragmatic approach: maintain the Ruby code but only port functionality to Haskell when
we could add meaningful value in the process. This meant our Ruby codebase gradually became legacy
code, maintained but not actively developed. This transition — which we expected to take a couple of
years — has now stretched into its seventh year.</p>
<p>Here’s where the Clean Architecture approach began to work against us. As we hired more
Haskell-focused developers and our institutional knowledge of Ruby faded, those carefully crafted
abstraction layers became archaeological puzzles. Reverse-engineering what a piece of code actually
<em>did</em> — especially complex, multi-step operations with side effects — became a nightmare.</p>
<p>The very indirection that had made our ERP migration so smooth now made understanding our legacy
code incredibly difficult. We developed a habit of first <em>collapsing</em> all the abstraction layers
in any area we wanted to port, flattening the code so we could see what it actually accomplished.
The comprehensive unit tests we’d written for each abstraction layer had to be collapsed too,
accounting for all the edge cases across multiple interfaces.</p>
<p>What had once been our architectural strength became a significant liability, made worse as fewer
team members retained working knowledge of Ruby.</p>
<h2 id="the-missing-framework">The missing framework</h2>
<p>This experience taught us something important about how we approach system design. We had excellent
resources for many aspects of software engineering — <a href="https://www.oreilly.com/library/view/working-effectively-with/0131177052/">Working Effectively with Legacy Code</a> is
brilliant for dealing with inherited technical debt, <a href="https://martinfowler.com/books/refactoring.html">Refactoring</a>
gives teams techniques and a common language around good (safe!) refactoring technique, and books
like Clean Architecture provide powerful patterns for active development of object-oriented
codebases.</p>
<p>But we lacked frameworks that would have helped us think about the <em>entire lifecycle</em> of a system.
We didn’t have good guidance on which architectural patterns are appropriate for different stages of
a system’s life, or clear warnings about the long-term costs of our design decisions.</p>
<p>There are plenty of blog posts discussing the abstract costs of indirection and over-abstraction.
What I’ve shared here is a concrete example of when and how a team actually pays those costs, and
how difficult it was for us to anticipate them. Our Clean Architecture approach was absolutely the
right choice for an actively developed Ruby system facing integration challenges. But it became the
wrong choice for a legacy Ruby system being gradually replaced by Haskell.</p>
<h2 id="designing-for-tomorrows-problems">Designing for tomorrow’s problems</h2>
<p>The lesson isn’t that Clean Architecture is bad — it’s that every architectural decision involves
tradeoffs across time. The abstraction layers that make active development and integration seamless
can make legacy code maintenance significantly harder. The interfaces that provide flexibility
during a system’s growth phase can become obstacles during its sunset phase.</p>
<p>Perhaps what we need is a more nuanced view of system architecture — one that acknowledges that the
“right” design depends not just on current requirements, but on the likely evolution of the system
over its entire lifespan. Sometimes the most maintainable code is the most obvious code, even if
it’s less elegant or flexible. Sometimes in pursuing the honorable goal of DRYing (Don’t Repeat
Yourself) up our code, we end up with a more complex system. We now talk about “complexity budgets” -
the idea that a team of a particular size can only support a certain amount of complexity across our
systems - and use that to guide our design decisions.</p>
<p>Are you:</p>
<ul>
<li>designing for extensibility? Then a pattern like Clean Architecture is a great fit for
object-oriented codebases, and <a href="/integrating-effectful-and-persistent.html#a-short-introduction-to-effectful">effect systems</a>
provide similar benefits for functional programmers.</li>
<li>designing for maintainability? You’ll want to reduce the abstract concepts while
maintaining separate, concrete classes/modules with a clear single responsibility.</li>
<li>designing for replacement? Then you want your code to read like an executable description of what
the legacy system does <em>right now</em>, end-to-end, so that it can be easily ported.</li>
</ul>
<p>Abstractions come with ongoing maintenance costs, but they only deliver value when you’re actively
switching between the things they abstract. As systems evolve, maintainers must decide when these
abstractions stop paying off. In our experience, legacy systems rarely benefit from keeping any of
these abstractions.</p>
<p>As we migrate to Haskell, we’re putting this lesson into practice. We’re being more deliberate about
when abstraction truly serves us — and when directness is the smarter long-term bet. The code we
write today becomes tomorrow’s legacy system, and our future selves and colleagues deserve better
than having to excavate meaning from layers of forgotten abstractions.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Tue, 22 Jul 2025 12:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/designing-for-the-different-stages-of-a-systems-life.html</guid>
    <dc:creator>Mike Webb</dc:creator>
</item>
<item>
    <title>Integrating Effectful and Persistent</title>
    <link>https://exploring-better-ways.bellroy.com/integrating-effectful-and-persistent.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Integrating Effectful and Persistent</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/jack.png"
              alt="Jack Kelly profile image"
            />
            <a class="no-underline font-bold" href="/contributors/jack_kelly.html">Jack Kelly</a>
            <small>2025-04-17</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>Bellroy’s Tech Team carefully curates the set of libraries and
techniques approved for general use in our codebases. We have a
process for experimenting with and approving new libraries and
techniques, and while we’ll write about that experimentation process
another time, today we’re going to focus on the interaction between
two of those experiments: the
<a href="https://hackage.haskell.org/package/effectful"><code>effectful</code></a>
effect-system library and the
<a href="https://hackage.haskell.org/package/persistent"><code>persistent</code></a>
database access library.</p>
<h2 id="a-short-introduction-to-effectful">A short introduction to <code>effectful</code></h2>
<p>Effect system libraries allow the programmer to define “effects”:
collections of related operations like “read-only access to a data
store”. Abstract console I/O is the canonical example used in nearly
every tutorial, and is represented in the Haskell <code>effectful</code>
library like this:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Console</span><span class="ot"> ::</span> <span class="dt">Effect</span> <span class="kw">where</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">GetLine</span><span class="ot"> ::</span> <span class="dt">Console</span> m <span class="dt">Text</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">PutLine</span><span class="ot"> ::</span> <span class="dt">Text</span> <span class="ot">-&gt;</span> <span class="dt">Console</span> m ()</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- `makeEffect` is from package `effectful-th`.</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- It automatically generates functions corresponding to each</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- constructor of an effect type. Here, it generates:</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- * getLine :: Console :&gt; es =&gt; Eff es Text</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- * putLine :: Console :&gt; es =&gt; Text -&gt; Eff es ()</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="co">-- The `Console :&gt; es` constraint indicates that these functions use</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="co">-- the `Console` effect. `Eff es` is a Monad, and the `es` type</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="co">-- variable stands for &quot;effects&quot; or &quot;effect set&quot;.</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="co">-- Read the `:&gt;` operator as &quot;is in&quot;: &quot;Console is in the effect set&quot;</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="op">$</span>(makeEffect &#39;<span class="dt">&#39;Console</span>)</span></code></pre></div>
<p>Programmers using the effect write their code against these generated
functions, and get type-level tracking of which effects their code
uses:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">greet ::</span> <span class="dt">Console</span> <span class="op">:&gt;</span> es <span class="ot">=&gt;</span> <span class="dt">Eff</span> es ()</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>greet <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  putLine <span class="st">&quot;What is your name?&quot;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  name <span class="ot">&lt;-</span> <span class="fu">getLine</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  putLine <span class="op">$</span> <span class="st">&quot;Hello, &quot;</span> <span class="op">&lt;&gt;</span> name <span class="op">&lt;&gt;</span> <span class="st">&quot;!&quot;</span></span></code></pre></div>
<p>This <code>greet</code> function has two important features: it tracks <em>that</em> it
uses the <code>Console</code> effect, while having no opinion about <em>how</em> the
<code>Console</code> effect is implemented. We can actually provide multiple
implementations (called “effect handlers”) for a single effect, and
choose whichever one makes the most sense for our needs:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Discharge a `Console` effect by performing I/O actions against</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- standard input/ouput.</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- `IOE :&gt; es` is `effectful`&#39;s &quot;arbitrary I/O&quot; effect.</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="ot">runConsoleIO ::</span> (<span class="dt">IOE</span> <span class="op">:&gt;</span> es) <span class="ot">=&gt;</span> <span class="dt">Eff</span> (<span class="dt">Console</span> <span class="op">:</span> es) a <span class="ot">-&gt;</span> <span class="dt">Eff</span> es a</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- Purely discharge a `Console` effect by providing a list of lines to</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co">-- use for input. Return a list of output lines along with the result.</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co">-- This can be good for testing.</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="ot">runConsolePure ::</span> [<span class="dt">Text</span>] <span class="ot">-&gt;</span> <span class="dt">Eff</span> (<span class="dt">Console</span> <span class="op">:</span> es) a <span class="ot">-&gt;</span> <span class="dt">Eff</span> es ([<span class="dt">Text</span>], a)</span></code></pre></div>
<p>These features feed into the main benefits we hope to gain from an
effect system:</p>
<ul>
<li>We’d like to write code that’s only aware of the abstract effects it
needs to do its job; and</li>
<li>We’d like to write better tests by providing alternative effect
handlers for our testing code to use. We might want to turn off or
capture logging, or replace a remote data store with an in-memory
one.</li>
</ul>
<h2 id="our-experiment">Our experiment</h2>
<p>Effect system libraries are an active research area in the Haskell
world, and that means there are several promising libraries offering
different tradeoffs between (at least) performance, developer
ergonomics, scary type signatures, and learnability. We ran our
experiment in a brand new project, and when we started that project in
early 2023, <code>effectful</code> seemed like the most promising balance between
those factors.</p>
<p>That project was also the first time we had to directly interface our
Haskell code with a PostgreSQL database, and for that we decided to
trial the <code>persistent</code> library. Combining this with <code>effectful</code> proved
challenging because <code>persistent</code>’s typeclasses like
<a href="https://hackage-content.haskell.org/package/persistent-2.15.1.0/docs/Database-Persist-Class.html#t:PersistStoreRead"><code>PersistStoreRead</code></a>
are fundamentally coupled to <code>MonadIO</code>, and much of the value of using
an effect system comes from tracking what effects are performed
instead of settling for “pure functions” and “can perform arbitrary
I/O”.</p>
<p>As part of the <code>effectful</code> experiment, we wanted to interpret
domain-specific effects into a “<code>Persistent</code> effect”, and wanted an
effect with the following properties:</p>
<ol>
<li><p>Not needing to write wrappers for each individual <code>persistent</code>
operation. At the experimentation stage, this is too much work for
too little benefit.</p></li>
<li><p>Using a <code>persistent</code> action should not add an obvious
<a href="https://hackage.haskell.org/package/effectful-core-2.5.1.0/docs/Effectful.html#t:IOE"><code>IOE</code></a>
effect, which is <code>effectful</code>’s marker that the function can perform
arbitrary IO. We’d like an effect to indicate that
<code>persistent</code>-using functions are “doing database operations”
without having to write <code>IOE :&gt; es</code> everywhere.</p></li>
<li><p>It should be possible to share a <code>persistent</code> backend (e.g.,
<a href="https://hackage.haskell.org/package/persistent-2.14.4.4/docs/Database-Persist-SqlBackend-Internal.html#t:SqlBackend"><code>SqlBackend</code></a>) with
specific subcomputations, so that we can provide backends either
from a <a href="https://hackage.haskell.org/package/resource-pool-0.4.0.0/docs/Data-Pool.html#t:Pool">resource pool</a>
or share a single backend with the entire program.</p></li>
<li><p>Whether we use a resource pool should be invisible to code that
uses our effect. We don’t want arbitrary functions to be able to
manipulate the pool itself; our effect handler should be
responsible for loaning out backend values from the resource pool.</p></li>
</ol>
<p>To avoid getting bogged down writing an <code>effectful</code>-flavoured binding
to all of <code>persistent</code>, we instead defined a slightly dodgy <code>Persist</code>
effect that’s parameterised by the <code>backend</code> it looks for, and whose
only job is to hold <code>persistent</code> actions:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Persist</span><span class="ot"> backend ::</span> <span class="dt">Effect</span> <span class="kw">where</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">LiftPersist</span><span class="ot"> ::</span> <span class="dt">ReaderT</span> backend <span class="dt">IO</span> a <span class="ot">-&gt;</span> <span class="dt">Persist</span> backend m a</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- Generate the `liftPersist` function and other machinery:</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- liftPersist ::</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co">--   Persist backend :&gt; es =&gt;</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="co">--   ReaderT backend IO a -&gt;</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co">--   Eff es a</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="co">--</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a><span class="co">-- EDIT(2025-04-22): A previous version of this post erroneously</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="co">-- claimed that you had to write `liftPersist` by hand,</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a><span class="co">-- because of the type parameter in the effect.</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a><span class="op">$</span>(makeEffect &#39;<span class="dt">&#39;Persist</span>)</span></code></pre></div>
<p>The other half of setting up an effect is providing ways to discharge
it. We’ll need two — one to provide a <code>backend</code> directly, and one
that sources it from a resource pool. <code>runPersistDirect</code> is quite
straightforward, but <code>runPersistFromPool</code> requires us to unlift <code>Eff es</code> down to <code>IO</code> so that we can use
<a href="https://hackage.haskell.org/package/resource-pool-0.4.0.0/docs/Data-Pool.html#v:withResource"><code>Data.Pool.withResource</code></a>:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- | Discharge a @&#39;Persist&#39; backend@ effect by providing a backend to use.</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="ot">runPersistDirect ::</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>  <span class="kw">forall</span> backend es a<span class="op">.</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>  (<span class="dt">IOE</span> <span class="op">:&gt;</span> es) <span class="ot">=&gt;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>  backend <span class="ot">-&gt;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Eff</span> (<span class="dt">Persist</span> backend &#39;<span class="op">:</span> es) a <span class="ot">-&gt;</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Eff</span> es a</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>runPersistDirect backend <span class="ot">=</span> interpret <span class="op">$</span> \_ <span class="ot">-&gt;</span> \<span class="kw">case</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>  <span class="dt">LiftPersist</span> m <span class="ot">-&gt;</span> liftIO <span class="op">$</span> runReaderT m backend</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="co">-- | Discharge a @&#39;Persist&#39; backend@ effect by loaning a backend from</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="co">-- a resource pool. The backend is returned to the pool after use.</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="ot">runPersistFromPool ::</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>  <span class="kw">forall</span> backend es a<span class="op">.</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>  (<span class="dt">IOE</span> <span class="op">:&gt;</span> es) <span class="ot">=&gt;</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Pool</span> backend <span class="ot">-&gt;</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Eff</span> (<span class="dt">Persist</span> backend &#39;<span class="op">:</span> es) a <span class="ot">-&gt;</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Eff</span> es a</span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a>runPersistFromPool pool eff <span class="ot">=</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a>  withEffToIO (<span class="dt">ConcUnlift</span> <span class="dt">Ephemeral</span> <span class="dt">Unlimited</span>) <span class="op">$</span> \runInIO <span class="ot">-&gt;</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a>    liftIO <span class="op">.</span> withResource pool <span class="op">$</span> \backend <span class="ot">-&gt;</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>      runInIO <span class="op">$</span> runPersistDirect backend eff</span></code></pre></div>
<h2 id="using-the-persist-backend-effect">Using the <code>Persist backend</code> effect</h2>
<p>Actually using this effect is about as simple as any other <code>effectful</code>
effect:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- | Insert a single record, as a minimal example</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="ot">persistSomeData ::</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Persist</span> <span class="dt">SqlBackend</span> <span class="op">:&gt;</span> es <span class="ot">=&gt;</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">SomeData</span> <span class="ot">-&gt;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Eff</span> es <span class="dt">SomeDataId</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>persistSomeData someData <span class="ot">=</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>  liftPersist <span class="op">@</span><span class="dt">SqlBackend</span> <span class="op">$</span> Persist.insert someData</span></code></pre></div>
<h2 id="conclusions">Conclusions</h2>
<p>This approach has worked fairly well during our experiment, and has
some benefits that made us think it was worth writing up:</p>
<ul>
<li>It captures the programmer’s intent quite well — a <code>Persist backend :&gt; es</code> constraint clearly shows “database access happens
here”;</li>
<li>We didn’t need a perfectly idiomatic effect definition to begin
writing useful code against the effect;</li>
<li>After we worked through all the details and settled on the
<code>LiftPersist</code> idea, it was reasonably quick to set up;</li>
<li>This technique can work for a large class of libraries (even ones
that are quite tightly coupled to <code>IO</code>); and</li>
<li>Other developers didn’t need to touch the effect definition once it
was written.</li>
</ul>
<p>However, this quick-and-dirty approach has some pretty noticeable
deficiencies that it’s worth being aware of:</p>
<ul>
<li>The biggest problem is that <code>liftPersist . lift</code> can smuggle an
arbitrary <code>IO a</code> action, so the <code>Persist backend</code> effect isn’t
making an honest “only database access happens here” promise. Our
developers are well-intentioned, so this is fine for a prototype;</li>
<li>The effect itself is opaque — the only possible interpretations
are variations on “just run it”; and</li>
<li>It’s very easy to let the <code>Persist backend :&gt; es</code> constraint float
to the outermost layer of your program and discharge it using
<code>runPersistFromPool</code>, not realising this shares a single backend
with the whole program and defeats the entire point of the resource
pool.</li>
</ul>
<p>As an experimental technique, we’re pretty happy overall with how this
style of effect turned out. Using a simpler effect definition let us
run a cheap experiment with <code>persistent</code>, and it’s always possible to
refine the effect later. This is the kind of refactoring Haskell
excels at — we can remove <code>liftPersist</code> whenever we want and chase
down the type errors until everything compiles, defining more precise
operations under the <code>Persist backend</code> effect as they’re needed.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Thu, 17 Apr 2025 12:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/integrating-effectful-and-persistent.html</guid>
    <dc:creator>Jack Kelly</dc:creator>
</item>
<item>
    <title>Open Source at Bellroy: Supporting Old GHC Versions</title>
    <link>https://exploring-better-ways.bellroy.com/open-source-at-bellroy-supporting-old-ghc-versions.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Open Source at Bellroy: Supporting Old GHC Versions</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/jack.png"
              alt="Jack Kelly profile image"
            />
            <a class="no-underline font-bold" href="/contributors/jack_kelly.html">Jack Kelly</a>
            <small>2025-03-19</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>There has been some good discussion on the
<a href="https://discourse.haskell.org/t/how-much-effort-does-backwards-compatibility-require-from-library-authors/11584">Haskell.org Discourse</a>
recently about how many GHC versions it’s reasonable for a maintainer
to support. Some maintainers minimise their maintenance burden by
rotating out support for anything older than the three most recent GHC
major releases; others drop old GHC versions with extreme reluctance.</p>
<p>Bellroy’s primary business is making and selling <a href="https://bellroy.com">good things</a>.
To help the rest of the business make and sell these good things,
Bellroy’s Tech Team builds and maintains software. We are always
grateful for the massive ecosystem of open-source software that we
build upon, and to the community of maintainers who keep it
alive. Bellroy talks a lot about using <a href="https://bellroy.com/responsible-business">business as a force for good</a>;
in the open-source realm, that means trying to “pay forward” the gifts
we’ve built upon. Concretely, this means trying to work upstream-first
wherever it makes sense, and releasing useful open-source packages of
our own.</p>
<p>This puts us in a challenging position. We want our open-source
releases to be broadly useful, and at the same time we cannot take on
an unlimited maintenance burden. Tech Team plans and executes its work
using a variant of the <a href="https://basecamp.com/shapeup">Shape Up</a>
process. We work in <a href="https://exploring-better-ways.bellroy.com/using-shape-up-what-works-what-weve-changed-whats-next.html">eight-week cycles</a>,
spending six weeks on project work followed by two weeks of “cool
down”. Routine dependency updates and open-source maintenance all have
to fit into the cool-down period.</p>
<p>We have an internal document describing how to maintain our
open-source packages, and thought we’d share our current thinking on
GHCs, library bounds and related issues. This is not the final word
from us or even a general recommendation. It’s what we’re doing at the
moment, it might change, and it is most definitely not an SLA.</p>
<h2 id="which-ghcs-to-target">Which GHCs to target?</h2>
<p>Within those constraints, which GHCs do we support for an open-source
release? First and foremost, we <strong>must</strong> maintain support for whatever
GHC major release is used in our internal Haskell monorepo. Currently,
that’s GHC 9.6.</p>
<p>We also aim to officially support any compiler marked “suitable for
use” on the <a href="https://gitlab.haskell.org/ghc/ghc/-/wikis/GHC-status">GHC GitLab’s wiki</a>, but
that’s less of a sure thing. <a href="https://exploring-better-ways.bellroy.com/the-history-of-nix-at-bellroy.html#phase-1-developer-shells-via-shellnix">We use Nix for developer shells</a>,
and for our open-source libraries, we need to keep the amount of Nix
wrangling manageable. These packages use a <a href="https://github.com/bellroy/bellroy-nix-foss/">Nix Flake</a>
that only provides GHC, and leaves package selection to the <code>cabal</code>
command. If our package’s dependencies haven’t yet updated, or the
newest GHC isn’t yet in <a href="https://github.com/nixos/nixpkgs"><code>nixpkgs</code></a>,
we might not be able to support a GHC recently marked “suitable for
use”. In such instances, we offer help where we can, but usually have
to leave the GHC upgrades for a future project cycle.</p>
<p>When we support a GHC version, we list it in the <code>tested-with</code> field
of the package’s <code>.cabal</code> file. The
<a href="https://github.com/Kleidukos/get-tested"><code>get-tested</code> tool</a> reads
this field and lets us run a <a href="https://github.com/features/actions">GitHub Actions</a>
matrix of all the GHC versions we support.</p>
<h3 id="which-old-versions-to-remove">Which old versions to remove?</h3>
<p>Supporting multiple GHC versions sometimes means adding compatibility
cruft to a package. The two most common forms of this are <a href="https://en.wikipedia.org/wiki/C_preprocessor">CPP
directives</a> in Haskell
sources and <code>if impl(ghc &gt;=x.y)</code> conditions in <code>.cabal</code> files. Such
cruft might be necessary to support occasional major releases marked
“suitable for use”, but can accumulate over a long range of GHC
releases. We don’t want the maintenance burden of a package to grow
without bound, but we also don’t want to needlessly remove support for
older versions when it doesn’t cost additional effort.</p>
<p>The compromise we’ve settled on is this: we don’t remove old GHC
versions from <code>tested-with</code> until supporting them requires more code
than just supporting the “suitable for use” versions.</p>
<h2 id="which-haskell-dialect-to-use">Which Haskell dialect to use?</h2>
<p>The <a href="https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/control.html#controlling-editions-and-extensions"><code>GHC202X</code> language editions</a>
are extremely convenient. We use them throughout our monorepo, but
open-source releases use <code>Haskell2010</code> and explicit <code>{-# LANGUAGE #-}</code>
pragmas. This is mostly to make things easier for the emerging
<a href="https://github.com/augustss/MicroHs">MicroHs</a> compiler, and to avoid
raising the GHC lower bound just for a little convenience.</p>
<p>For similar reasons, we avoid convenience extensions such as
<code>-XBlockArguments</code> (GHC 8.6.1) and <code>-XImportQualifiedPost</code> (GHC
8.10.1). These extensions only add alternative syntax, and although
they’re generally old enough to not really factor into GHC bounds, it
doesn’t seem worth excluding old versions or alternate compilers by
requiring them.</p>
<h2 id="what-library-bounds-to-use-for-dependencies">What library bounds to use for dependencies?</h2>
<p>Setting library bounds correctly still feels like a bit of an art. One
helpful resource is the GHC wiki: its
<a href="https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/libraries/version-history">table of boot library versions</a>,
lists the version of <code>base</code> and other fundamental libraries (e.g.,
<code>text</code>) that each GHC release ships with. We set <code>base</code> bounds to
admit every GHC version listed in <code>tested-with</code>, and ensure that other
boot libraries have bounds which admit the library versions bundled
with each GHC.</p>
<p>For other dependencies, we start with the version we actually used,
and bound it above by the next major version (which is when the
<a href="https://pvp.haskell.org/">Package Version Policy</a> allows the next
breaking change). The <code>cabal outdated</code> command then warns us about new
package versions we need to accommodate. If we have to make code
changes, we cut a new release; otherwise we can revise the bounds on
<a href="https://hackage.haskell.org">Hackage</a>.</p>
<p>As with old GHCs, we can’t spend unlimited effort supporting expansive
bounds, but for important libraries undergoing major change (e.g., the
<code>aeson-2.0</code> transition), we do try to support both versions at least
for a while.</p>
<h2 id="what-dependencies-are-worth-taking-on">What dependencies are worth taking on?</h2>
<p>We try to depend on the minimal set of libraries necessary to get the
job done, because dragging in half of Hackage to solve a simple
problem feels impolite. Concretely, this mostly means avoiding custom
prelude packages and convenient-but-heavy libraries like
<a href="https://hackage.haskell.org/package/string-interpolate"><code>string-interpolate</code></a>
(which needs
<a href="https://hackage.haskell.org/package/haskell-src-exts"><code>haskell-src-exts</code></a>),
and using the
<a href="https://github.com/stevenfontanella/microlens"><code>microlens-*</code></a> family
of lens libraries instead of
<a href="https://hackage.haskell.org/package/lens"><code>lens</code></a>. In many cases,
the <a href="https://hackage.haskell.org/package/generic-lens"><code>generic-lens</code></a>
and <a href="https://hackage.haskell.org/package/generic-optics"><code>generic-optics</code></a>
packages remove the need for library authors to provide lenses at all.</p>
<h2 id="conclusion">Conclusion</h2>
<p>We currently maintain a small handful of open-source Haskell libraries
and have more packages awaiting spin-out. Our experience so far is
that these principles strike a good balance between maintenance effort
and supporting a fair range of compilers and library
versions. Dependency update days have generally been smooth, and we
expect these processes to scale reasonably well as we release more
software to Hackage. We’re looking forward to announcing those new
packages when they’re ready.</p>
<p>And if we have to come up with something else, we’ll probably write
about that too.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Wed, 19 Mar 2025 12:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/open-source-at-bellroy-supporting-old-ghc-versions.html</guid>
    <dc:creator>Jack Kelly</dc:creator>
</item>
<item>
    <title>Bellroy Technology Team: 2024 in Review</title>
    <link>https://exploring-better-ways.bellroy.com/bellroy-technology-team-2024-in-review.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Bellroy Technology Team: 2024 in Review</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/mike_ai_avatar.jpeg"
              alt="Mike Webb profile image"
            />
            <a class="no-underline font-bold" href="/contributors/mike_webb.html">Mike Webb</a>
            <small>2025-02-14</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>2024 has been a transformative year for our Technology Team as Bellroy continues to grow from its
startup roots into a medium-sized company. We’ve tackled ambitious projects, celebrated significant
wins, and learned valuable lessons – especially about how plans often evolve in unexpected ways when
your company is scaling up.</p>
<p>One of our main focuses this year was streamlining internal operations through automation. Our
Production team now has a much smoother product data process, saving hours of manual work each week.
We’ve modernized our employee purchase program, letting our staff experience our customer journey
exactly as other customers do – which has already given us great insights into our user experience.
We’ve also automated much of our refunds processing, freeing up our Finance team to focus on more
strategic work. While these improvements might not be immediately visible to customers, they’ve been
crucial in keeping us nimble as we grow.</p>
<p>Our e-commerce platform saw some exciting improvements this year. We completely rebuilt our shopping
cart, making it significantly faster and giving customers real-time updates about promotions and
stock levels. We rolled out single-use promotion codes, letting us reward loyal customers with
special offers without the usual worries about codes being shared widely on social media. We’ve also
been continuously fine-tuning our checkout process through various experiments – though we’ll admit
not every test yielded the results we expected! And of course, throughout the year we supported all
of Bellroy’s product launches and campaigns, ensuring our platform delivered smooth experiences
during our busiest and most exciting moments.</p>
<p>We faced our share of challenges too. Our attempt to improve our A/B testing capabilities proved
trickier than anticipated, reminding us why maintaining our own platform can be harder than using
off-the-shelf solutions. We attempted to migrate our legacy Rails solution to a more cutting-edge
solution relying on <a href="https://aws.amazon.com/developer/application-security-performance/articles/cloudfront-edge-functions">AWS Cloudfront Edge Functions</a>
and <a href="https://kafka.apache.org/">Kafka</a>. We underestimated the complexity in incrementally
migrating away from the legacy system while implementing the new system, and were unable to complete
the project in the six weeks that <a href="https://exploring-better-ways.bellroy.com/using-shape-up-what-works-what-weve-changed-whats-next.html">our Shape Up methodology</a>
dictates. We learned important lessons about what these technologies can and can’t do. Still, we
remain confident that building our own technology gives us the flexibility and control we need to
create the best possible experience for our customers; we’re taking another stab at a simpler
solution in 2025 without the use of Edge Functions. It’s possible that if we’d persisted with our
original plan we’d have succeeded, but be in a worse position architecturally. This is a major
strength of using Shape Up - being able to intelligently adjust or stop work based on what you learn
during development, rather than doggedly sticking to the plan.</p>
<p>We completed several projects this year where we aimed to automate manual processes
for other teams at Bellroy. These were always challenging projects, as they required us to understand
the intricacies of other teams’ workflows and systems. In helping us understand these processes, the
teams often uncovered problems or inconsistencies that the teams themselves hadn’t noticed before
the project started. While we think the projects added a huge amount of value, they almost
inevitably produced outcomes that did not resemble the original plans. This was a good reminder that
the best solutions often come from collaboration and iteration, rather than trying to predict
everything up front.</p>
<p>Behind the scenes, we’ve made solid progress moving our legacy Ruby code to Haskell, resulting in
better performance and stability. We’ve also improved how we handle technical documentation, setting
up automated systems to keep our team’s wiki documentation in sync with our code. This might sound
trivial, but it’s making a real difference in how efficiently we can maintain and improve our
platform as it grows more complex.</p>
<p><img src="/images/2412_haskell_vs_ruby.png" alt="A graph of Haskell vs Ruby lines of code since January 2022" /></p>
<p>We were proud to contribute to the Haskell open-source community in 2024, releasing the following
libraries out into the wild:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/servant-activeresource">servant-activeresource</a>: Servant endpoints compatible with Rails’s ActiveResources</li>
<li><a href="https://hackage.haskell.org/package/tasty-golden-extra">tasty-golden-extra</a>: Additional golden test helpers for the tasty-golden package</li>
<li><a href="https://hackage.haskell.org/package/unliftio-servant-server">unliftio-servant-server</a>: Use MonadUnliftIO on servant APIs</li>
</ul>
<p>Looking ahead to 2025, we see our role evolving alongside Bellroy’s growth. The Technology Team now
splits its focus between enhancing customer experience and improving company operations. We’re
expanding into new territory – from integrating with
<a href="https://bellroy.com/official-china-platforms">major Chinese e-commerce platforms</a> to
experimenting with alternative delivery methods in challenging markets. This year’s experiences have
taught us to stay adaptable and embrace solutions that might look different from our initial plans.
As Bellroy continues to grow, we’re excited to keep finding better ways to help both our customers
carry better and our company work smarter.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Fri, 14 Feb 2025 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/bellroy-technology-team-2024-in-review.html</guid>
    <dc:creator>Mike Webb</dc:creator>
</item>
<item>
    <title>Solving a ResourceT-related space leak in production</title>
    <link>https://exploring-better-ways.bellroy.com/solving-a-resourcet-related-space-leak-in-production.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Solving a ResourceT-related space leak in production</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="https://avatars.githubusercontent.com/u/16440269"
              alt="Zelin Feng profile image"
            />
            <a class="no-underline font-bold" href="/contributors/zelin_feng.html">Zelin Feng</a>
            <small>2024-12-12</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>Working with Haskell is fun, until you have space leaks — especially when they
occur in AWS Lambda Functions. In this blog post, we focus on how we plugged a
particularly nasty space leak. In a future post, we will introduce a
profiling tool for Haskell applications running on <a href="https://aws.amazon.com/pm/lambda">AWS Lambda</a>.</p>
<h2 id="problem">Problem</h2>
<p>We observed one of our Lambda Functions dying of OOM (Out Of Memory) failures,
seemingly at random. The function had 1GB of memory allocated, and there seemed
to be a big disconnect between the work it was doing and its memory utilization.</p>
<h2 id="hunting-down-space-leaks">Hunting down space leaks</h2>
<p>In a docker container with memory constraints, the <a href="https://stackoverflow.com/questions/72185669/what-is-the-real-memory-available-in-docker-container"><code>free</code> command shows the memory
size of the host system instead of the container’s memory constraints.</a></p>
<p>For AWS Lambda Functions, we suspected that the situation might be similar. If
there is anything special that the runtime system of a programming language
needs to do to detect the correct memory limit, it might be done in those
official Lambda Function runtimes — Golang and Python, for example — but not in
Haskell.</p>
<p>If GHC RTS (Runtime System) is reading incorrect memory constraints, it may
decide to defer garbage collection because it
thinks there is still available memory.</p>
<p>To verify or eliminate this hypothesis, we added the <a href="https://downloads.haskell.org/ghc/9.10-latest/docs/users_guide/runtime_control.html#rts-flag--M%20%E2%9F%A8size%E2%9F%A9">RTS option
<code>-M1000M</code></a>
and increased the Lambda Function memory to 1792MB. Under these conditions, RTS should try its best to GC
before the heap reaches 1000MB.</p>
<p>However, the Lambda Function still ran Out of Memory after a while:
<img src="/images/20241212-solving-space-leak-resourcet/lambda_heap_exhausted.png" alt="lambda heap exhausted" /></p>
<p>This result showed that a space leak existed, but we couldn’t reproduce
this problem in our local or testing environments.</p>
<h2 id="locating-the-space-leak-using-profiling">Locating the space leak using profiling</h2>
<p>We developed a tool to profile Haskell in AWS Lambda Functions running Haskell
binaries, by sending the eventlog to <a href="https://aws.amazon.com/pm/serv-s3">Amazon S3</a>. Here is the generated
eventlog (converted to HTML by
<a href="https://mpickering.github.io/eventlog2html/">eventlog2html</a>):</p>
<p><img src="/images/20241212-solving-space-leak-resourcet/resourcet_space_leak_eventlog.png" alt="ResourceT space leak eventlog" /></p>
<p>The memory usage and heap size both kept increasing over more than 10 minutes.</p>
<p>Why was the eventlog collected over more than 10 minutes when the
Lambda Function timeout was set to a smaller value?</p>
<p>To answer this question, we needed to understand the execution model of Lambda
Function. When a request arrives, AWS starts a new instance of our Lambda Function
if there isn’t one already running. Each instance handles one request at any point
in time. When a response has been returned, AWS can still keep your Function
instance alive for an arbitrary period of time and reuse it when a new
request arrives. Thus, if a memory block is somehow held in the global
environment (e.g. some top level monad) and isn’t freed between requests, the
Lambda Function will eventually run Out of Memory if it keeps receiving requests.</p>
<p>In the area chart and “Detailed” tab in the HTML report generated by
<code>eventlog2html</code>, we found <code>decodeEventASN1Repr</code> and many related functions
consuming a lot of memory. We started to suspect
<a href="https://hackage.haskell.org/package/amazonka"><code>amazonka</code></a>, because in this
Lambda Function we exclusively use <code>amazonka</code> to make network requests.</p>
<p><img src="/images/20241212-solving-space-leak-resourcet/resourcet_space_leak_detailed_tab.png" alt="ResourceT space leak detailed tab" /></p>
<p>Since the profiling result suggested that many AWS responses couldn’t be freed
by the garbage collector, we decided to take a closer look at the source code of
<code>amazonka</code>.</p>
<h2 id="diving-into-the-code">Diving into the code</h2>
<p>We examined all AWS calls in our Lambda Function that had the space leak. It
turned out that the <code>amazonka</code> implementation of the AWS
<a href="https://docs.aws.amazon.com/step-functions/latest/apireference/API_SendTaskSuccess.html">SendTaskSuccess</a>
API was different from others. In <code>amazonka</code>, this API is implemented in
<a href="https://hackage.haskell.org/package/amazonka-stepfunctions-2.0/docs/Amazonka-StepFunctions-SendTaskSuccess.html">Amazonka.StepFunctions.SendTaskSuccess</a>.
All <code>amazonka</code> request types have to provide an instance of the <code>AWSRequest</code>
typeclass.
<a href="https://hackage.haskell.org/package/amazonka-stepfunctions-2.0/docs/src/Amazonka.StepFunctions.SendTaskSuccess.html#line-108">Here</a>
is how the <code>AWSRequest</code> instance is implemented on <code>SendTaskSuccess</code>:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">instance</span> <span class="dt">Core.AWSRequest</span> <span class="dt">SendTaskSuccess</span> <span class="kw">where</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  <span class="kw">type</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="dt">AWSResponse</span> <span class="dt">SendTaskSuccess</span> <span class="ot">=</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>      <span class="dt">SendTaskSuccessResponse</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>  request overrides <span class="ot">=</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    Request.postJSON (overrides defaultService)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>  response <span class="ot">=</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>    Response.receiveEmpty</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>      ( \s h x <span class="ot">-&gt;</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>          <span class="dt">SendTaskSuccessResponse&#39;</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>            <span class="op">Prelude.&lt;$&gt;</span> (Prelude.pure (Prelude.fromEnum s))</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>      )</span></code></pre></div>
<p>In <code>response</code>, it calls <code>receiveEmpty</code>, which is <a href="https://hackage.haskell.org/package/amazonka-core-2.0/docs/src/Amazonka.Response.html#receiveEmpty">implemented as</a>:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">receiveEmpty ::</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">MonadResource</span> m <span class="ot">=&gt;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  (<span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">ResponseHeaders</span> <span class="ot">-&gt;</span> () <span class="ot">-&gt;</span> <span class="dt">Either</span> <span class="dt">String</span> (<span class="dt">AWSResponse</span> a)) <span class="ot">-&gt;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  (<span class="dt">ByteStringLazy</span> <span class="ot">-&gt;</span> <span class="dt">IO</span> <span class="dt">ByteStringLazy</span>) <span class="ot">-&gt;</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Service</span> <span class="ot">-&gt;</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">Proxy</span> a <span class="ot">-&gt;</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">ClientResponse</span> <span class="dt">ClientBody</span> <span class="ot">-&gt;</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>  m (<span class="dt">Either</span> <span class="dt">Error</span> (<span class="dt">ClientResponse</span> (<span class="dt">AWSResponse</span> a)))</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>receiveEmpty f _ <span class="ot">=</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>  stream <span class="op">$</span> \r s h _ <span class="ot">-&gt;</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>    liftIO (Client.responseClose r) <span class="op">$&gt;</span> f s h ()</span></code></pre></div>
<p>Despite the complex parameters, the only
relevant detail is that in <code>receiveEmpty</code>, <code>responseClose</code> is called without
reading the entire HTTP response.</p>
<p>This seems innocent — and it is. The problem laid in <code>http-conduit</code>.
<code>amazonka</code> calls the function
<a href="https://hackage.haskell.org/package/http-conduit-2.3.9/docs/Network-HTTP-Conduit.html#v:http"><code>http</code></a>
to implement its network requests. At the time of our investigation, the <code>http</code>
function was implemented as:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ot">http ::</span> <span class="dt">MonadResource</span> m</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>     <span class="ot">=&gt;</span> <span class="dt">Request</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>     <span class="ot">-&gt;</span> <span class="dt">Manager</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>     <span class="ot">-&gt;</span> m (<span class="dt">Response</span> (<span class="dt">ConduitM</span> i <span class="dt">S.ByteString</span> m ()))</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>http req man <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>    (key, res) <span class="ot">&lt;-</span> allocate (Client.responseOpen req man) Client.responseClose</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>    <span class="fu">return</span> res { responseBody <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>                   HCC.bodyReaderSource <span class="op">$</span> responseBody res</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>                   release key</span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>               }</span></code></pre></div>
<p>Note that
<a href="https://hackage.haskell.org/package/resourcet-1.3.0/docs/Control-Monad-Trans-Resource.html#v:allocate"><code>allocate</code></a>
is called — it’s a function from <code>ResourceT</code>. Many Haskell developers may
have been using <code>ResourceT</code> in their daily job without realizing what it does
under the hood. <code>ResourceT</code> maintains a mutable <code>Map</code> of external resources with
their cleanup functions. <code>runResourceT</code> automatically calls the cleanup
functions right before it exits.</p>
<p>In many applications (including ours), <code>ResourceT</code> is added to the global
application-level monad transformer stack, and <code>runResourceT</code> only runs once
globally. Then, this global <code>ResourceT</code> becomes a global registry of resources.
Any registered resource is released either explicitly by calling
<a href="https://hackage.haskell.org/package/resourcet-1.3.0/docs/Control-Monad-Trans-Resource.html#v:release"><code>release</code></a>
or when the entire application exits. The garbage collector can’t free any heap
object if it’s directly or indirectly referenced by a registered resource.</p>
<p>In the implementation of function <code>http</code>, <code>release</code> is called only when the full
response body is consumed. But it’s not uncommon to only consume part of the
response body. You could be making a POST or PUT request and the only thing you
care in the response is if the status code is 200 or not. You could be reading a
file from S3 and optionally discard the remaining content if your program
decides to do so. In these scenarios, the resource won’t be released before the
end of <code>runResourceT</code>, resulting in a space leak.</p>
<h2 id="creating-a-minimal-reproducer">Creating a minimal reproducer</h2>
<p>To prove the theory above, we created a minimal reproducer so that we wouldn’t
need to test it in our production code.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ot">main ::</span> <span class="dt">IO</span> ()</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>main <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>  env <span class="ot">&lt;-</span> Amazonka.newEnv Amazonka.discover</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>  void <span class="op">.</span> Amazonka.runResourceT <span class="op">$</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>    for (<span class="fu">replicate</span> <span class="dv">1000</span> ()) <span class="op">$</span> \_ <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>      randomStr <span class="ot">&lt;-</span> <span class="fu">fmap</span> (T.pack <span class="op">.</span> <span class="fu">take</span> <span class="dv">1000000</span>) <span class="op">.</span> lift <span class="op">$</span> getRandomRs (<span class="ch">&#39;a&#39;</span>, <span class="ch">&#39;z&#39;</span>)</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>      void <span class="op">.</span> Amazonka.send env <span class="op">$</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>        S3.newPutObject <span class="st">&quot;bellroy-eventlog-test&quot;</span> <span class="st">&quot;test&quot;</span> (Amazonka.toBody randomStr)</span></code></pre></div>
<p>In this example, we make 1000 PutObject calls to Amazon S3, with random large
request bodies. Like <code>SendTaskSuccess</code>, the <code>amazonka</code> implementation of S3
<code>PutObject</code> operation also uses <code>receiveEmpty</code> internally. <code>runResourceT</code> exits
only after all 1000 requests are sent to S3. If our theory is correct, memory
usage of this test program should keep increasing until it runs Out of Memory.</p>
<p>Here is the profiling result before we apply any fix:
<img src="/images/20241212-solving-space-leak-resourcet/resourcet_space_leak_minimal_profiling_before.png" alt="Profiling on the minimal reproducer before the fix" /></p>
<p>There are two possible ways to fix this test program.</p>
<ol>
<li>Call <code>runResourceT</code> on each <code>Amazonka.send</code>.</li>
<li>In <code>http-client</code>, call <code>release</code> when <code>responseClose</code> is called.</li>
</ol>
<p>After implementing either fix, our new profiling output looks like this: <img src="/images/20241212-solving-space-leak-resourcet/resourcet_space_leak_minimal_profiling_after.png" alt="Profiling on the minimal reproducer after the fix" /></p>
<p>We submitted <a href="https://github.com/snoyberg/http-client/pull/539">a PR to
<code>snoyberg/http-client</code></a> so
with subsequent versions of <code>http-client</code>, our minimal reproducer no longer has
a space leak 🎉</p>
<h2 id="lessons-to-learn">Lessons to learn</h2>
<p>By calling any function whose type includes <code>MonadResource m =&gt; m a</code> or
<code>ResourceT m a</code>, you should be aware that some resource is registered
to your current monad transformer stack and may hold large blocks of memory.</p>
<p>To release that resource, either:</p>
<ol>
<li>Read the documentation of your library, and find out if there is any function
that allows releasing the resource explicitly. Call that function. In
<code>http-client</code> and <code>http-conduit</code>, the function to release a connection is
<a href="https://hackage.haskell.org/package/http-client-0.7.17/docs/Network-HTTP-Client.html#v:responseClose"><code>responseClose</code></a>.</li>
<li>Or, make your <code>runResourceT</code> scope smaller.</li>
</ol></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Thu, 12 Dec 2024 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/solving-a-resourcet-related-space-leak-in-production.html</guid>
    <dc:creator>Zelin Feng</dc:creator>
</item>
<item>
    <title>Save Effort: Build a Bash One-Liner</title>
    <link>https://exploring-better-ways.bellroy.com/save-effort-build-a-bash-one-liner.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Save Effort: Build a Bash One-Liner</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/luke.png"
              alt="Luke Worth profile image"
            />
            <a class="no-underline font-bold" href="/contributors/luke_worth.html">Luke Worth</a>
            <small>2024-08-22</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p><em>Preliminary disclaimer: the author firmly believes that an operating system
makes for a perfectly good IDE; he won’t be taking questions about this.</em></p>
<p>The Bellroy tech team has a longstanding goal to eliminate our Ruby
codebase<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>. To help us reach that goal, the whole team can see a
graph of LOC (Lines Of Code) currently stored in git for each programming
language we use, plotted over time. This is updated once per week, and watching
the Ruby line go downward encourages us to reduce the amount of Ruby we add
each week, and (more importantly) to increase the amount we delete. However,
like most metrics, it doesn’t <em>quite</em> directly measure progress toward our
goal: if someone splits a complicated line of code into multiple lines the code
becomes easier to understand, and thus easier to remove, but the total LOC
moves <em>away</em> from zero. This problem was brought to the forefront recently when
we instated automatic formatting of Ruby code using the
<a href="https://github.com/ruby-syntax-tree/syntax_tree">syntax_tree</a> tool (which,
like all our other formatters, has almost no configuration and so avoids
<a href="https://bikeshed.org">bike-shedding</a> about code layout). If this tool can’t
fit a single Ruby statement into one line it will usually break it into <em>many
lines</em>: when we run it on every Ruby file of the bellroy.com codebase we gain
about 6,700 lines of Ruby, which looks like we are adding Ruby code, which
makes us sad.</p>
<p>To deal with this problem I looked for an alternative metric that would be easy
to apply to our codebase, and robust to re-formatting. I quickly discovered the
<a href="https://en.wikipedia.org/wiki/ABC_Software_Metric">ABC Software Metric</a> which
is calculated from the number of <strong>A</strong>ssignments, <strong>B</strong>ranches, and
<strong>C</strong>onditionals in the code, and which looked like a much better proxy for how
much functionality lives in Ruby. And it turns out that <a href="https://docs.rubocop.org/rubocop/1.65/cops_metrics.html#metricsabcsize">RuboCop contains an
implementation of this
metric</a>!
But RuboCop is designed only to alert you when that metric is exceeded - at
present it provides no easy way to display the raw ABC size.</p>
<p>At this point, the obvious thought crossed my mind: perhaps I could fork
RuboCop, add the functionality I need, and submit a PR (because I am a good
person). But another thought also crossed my mind: that sounds like hard work
that I’d prefer to avoid. And of course, because I use UNIX<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a> (the world’s best
<a href="https://en.wikipedia.org/wiki/Integrated_development_environment">IDE</a>),
wouldn’t it be easier to use Rubocop as-is and use a bunch of duct tape and
WD-40<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a> to
get the outcome I want? The short answer is “yes, much easier.” Let’s work
through this together!</p>
<p>First we must ask the most basic question: is the raw metric exposed by RuboCop
<em>at all</em>? A quick search reveals <a href="https://github.com/rubocop/rubocop/blob/47cc359ba31e371ba262a903874693f0cec956af/lib/rubocop/cop/metrics/abc_size.rb#L42-L43">what seems to be an error message format
string</a>,
referencing something promisingly called <code>complexity</code>. Let’s see this error
message in action:</p>
<pre><code>$ nix run nixpkgs#rubocop -- --only Metrics/AbcSize config/application.rb
Inspecting 1 file
.

1 file inspected, no offenses detected</code></pre>
<p>Ah, the documentation says default <code>Max</code> value is 17. Let’s see what happens if
we set it to 0:</p>
<pre><code>$ cat &gt;.rubocop.yml &lt;&lt;&lt;&#39;Metrics/AbcSize: {Max: 0}&#39;
$ nix run nixpkgs#rubocop -- --no-display-cop-names --only Metrics/AbcSize config/application.rb
...snip...
Offenses:

...snip...
config/application.rb:59:3: C: Assignment Branch Condition size for settings is too high. [&lt;2, 4, 1&gt; 4.58/0]
  def self.settings ...
  ^^^^^^^^^^^^^^^^^
...snip...</code></pre>
<p>There it is! 4.58 is indeed greater than 0. RuboCop displays one of these
messages for each method, and now the end is in sight. Now all we have to do is
run this for every file in the codebase, extract all the complexity numbers,
and sum them up.</p>
<p>POSIX comes with a number of raw text processing tools but, no matter how
powerful they are, it is simply easier to deal with structured data. I’m
talking about JSON, a data format that began life as a quick way to transmit
data to and from web browsers, which is now the de-facto standard for encoding
structured data in plaintext; and <a href="https://docs.rubocop.org/rubocop/1.65/formatters.html#json-formatter">RuboCop knows how to produce
it</a>.</p>
<pre><code>$ nix run nixpkgs#rubocop -- --no-display-cop-names --only Metrics/AbcSize --format json config/application.rb
{&quot;metadata&quot;:{&quot;rubocop_version&quot;:&quot;1.62.1&quot;,&quot;ruby_engine&quot;:&quot;ruby&quot;,&quot;ruby_version&quot;:&quot;3.1.6&quot;,&quot;ruby_patchlevel&quot;:&quot;260&quot;,&quot;ruby_platform&quot;:&quot;arm64-darwin23&quot;},&quot;files&quot;:[{&quot;path&quot;:&quot;config/application.rb&quot;,&quot;offenses&quot;:[..snip..,{&quot;severity&quot;:&quot;convention&quot;,&quot;message&quot;:&quot;Assignment Branch Condition size for settings is too high. [&lt;2, 4, 1&gt; 4.58/0]&quot;,&quot;cop_name&quot;:&quot;Metrics/AbcSize&quot;,&quot;corrected&quot;:false,&quot;correctable&quot;:false,&quot;location&quot;:{&quot;start_line&quot;:59,&quot;start_column&quot;:3,&quot;last_line&quot;:61,&quot;last_column&quot;:5,&quot;length&quot;:114,&quot;line&quot;:59,&quot;column&quot;:3}},..snip..]}],..snip..}</code></pre>
<p>Looks worse to you and I, but for <a href="https://jqlang.github.io/jq/">jq</a> it is a
delectable treat. Let’s pull out the bit we care about:</p>
<pre><code>$ nix run nixpkgs#rubocop -- --no-display-cop-names --only Metrics/AbcSize --format json config/application.rb | jq -r &#39;.files[].offenses[].message&#39;
..snip..
Assignment Branch Condition size for settings is too high. [&lt;2, 4, 1&gt; 4.58/0]
..snip..</code></pre>
<p>Getting closer, but it’s time for a short Q&amp;A:</p>
<ul>
<li><p>Q: <em>What the heck is <code>nix run nixpkgs#rubocop --</code> and why don’t you use
<code>bundle exec rubocop</code> like a normal person?</em></p>
<p>A: <a href="https://nixos.org">nix</a> is awesome. We use it all the time, on Linux,
macOS, and Windows. Invoking RuboCop this way means I don’t have to install
it, nor add it to the Gemfile, nor modify the code at all. Anyone can run
this command on any Rails codebase and it’ll just work.</p></li>
<li><p>Q: <em>What was that <code>&lt;&lt;&lt;</code> thing before?</em></p>
<p>A: That is a <a href="https://www.gnu.org/software/bash/manual/bash.html#Here-Strings">here
string</a>. In
Bash, this basically means “send the adjoining text to standard input”. Bash
is full of hidden gems. I recommend periodically reading a random section of
<a href="https://www.gnu.org/software/bash/manual/bash.html">the reference manual</a>.</p></li>
<li><p>Q: <em>Isn’t that command getting long?</em></p>
<p>A: Yes, it is. That doesn’t matter because nobody else is going to see this
(except in my case, where I’m publishing it onto the internet). We want to
move quickly, not produce the “best” code.</p></li>
<li><p>Q: <em>How good is <code>jq</code>?</em></p>
<p>A: I know, right?!</p></li>
</ul>
<p>Unfortunately, for our ABC size calculator, we have eliminated all of the
available JSON so <code>jq</code> is no longer useful; but some folks predicted this
problem in the 1970s, and gave us AWK. Let’s try to pick out just the
complexity number (which is the <code>4.58</code> we keep seeing):</p>
<pre><code>$ MESSAGE=&#39;Assignment Branch Condition size for settings is too high. [&lt;2, 4, 1&gt; 4.58/0]&#39;
$ awk &#39;{print $13}&#39; &lt;&lt;&lt;&quot;$MESSAGE&quot;
4.58/0]</code></pre>
<p>And let’s get rid of that <code>/0]</code>:</p>
<pre><code>$ awk &#39;{gsub(/\/0]/, &quot;&quot;, $13); print $13}&#39; &lt;&lt;&lt;&quot;$MESSAGE&quot;
4.58</code></pre>
<p>Putting it all together:</p>
<pre><code>$ nix run nixpkgs#rubocop -- --no-display-cop-names --only Metrics/AbcSize --format json config/application.rb | jq -r &#39;.files[].offenses[].message&#39; | awk &#39;{gsub(/\/0]/, &quot;&quot;, $13); print $13}&#39;
..snip..
6.16
3.74
4.58
3
3.74</code></pre>
<p>That’s a list of ABC sizes of all methods in <code>config/application.rb</code>! We can use AWK to sum them up:</p>
<pre><code>$ nix run nixpkgs#rubocop -- --no-display-cop-names --only Metrics/AbcSize --format json config/application.rb | jq -r &#39;.files[].offenses[].message&#39; | awk &#39;{gsub(/\/0]/, &quot;&quot;, $13); sum += $13} END {print sum}&#39;
21.22</code></pre>
<p>“Do it on the whole codebase,” you yell. I yell back, “OK, give me a minute” and run:</p>
<pre><code>$ find . -name &#39;*.rb&#39;
..big list of every .rb file including vendored gems and other junk..</code></pre>
<p>This is not quite what you wanted, and a bit unprincipled. We don’t want the
files on the <em>filesystem</em>, we want the ones in the <em>codebase</em>. The canonical
list of <em>codebase</em> files lives in Git, so let’s ask Git instead:</p>
<pre><code>$ git ls-files | grep &#39;\.rb$&#39;
..exactly the list of Ruby files in our codebase..</code></pre>
<p>Good. Let’s plumb this into our “program”:</p>
<pre><code>$ nix run nixpkgs#rubocop -- --no-display-cop-names --only Metrics/AbcSize --format json $(git ls-files | grep &#39;\.rb$&#39;) | jq -r &#39;.files[].offenses[].message&#39; | awk &#39;{gsub(/\/0]/, &quot;&quot;, $13); sum += $13} END {print sum}&#39;
9910.94</code></pre>
<p>Voilà! The ABC size of bellroy.com’s Rails backend<a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a>.</p>
<p>I want to share this one-liner so that anyone can run it in any Ruby codebase,
but it still depends on the <code>.rubocop.yml</code> we deposited near the beginning. One
way around this is to simply include the <code>cat</code> step we did earlier, but that
would overwrite any existing <code>.rubocop.yml</code>, and would leave junk behind. Let
us reach into our bag of fancy Bash features that everyone should know, and
pull out <a href="https://www.gnu.org/software/bash/manual/bash.html#Process-Substitution">process
substitution</a>.
If you pass <code>--config some_file.yml</code> to RuboCop, it will read its configuration
from <code>some_file.yml</code> instead of <code>.rubocop.yml</code>. If you pass <code>--config &lt;(echo 'Metrics/AbcSize: {Max: 0}')</code>, RuboCop will read its configuration from a
temporary named pipe which appears to contain the desired configuration. That
lets us distribute a truly stand-alone tool that can run anywhere and leaves no
trace, and we didn’t need to write any Ruby to do it. Here’s the final version,
formatted for readability:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">nix</span> run nixpkgs#rubocop <span class="at">--</span> <span class="dt">\</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>        <span class="at">--config</span> <span class="op">&lt;(</span><span class="bu">echo</span> <span class="st">&#39;Metrics/AbcSize: {Max: 0}&#39;</span><span class="op">)</span> <span class="dt">\</span></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a>        <span class="at">--no-display-cop-names</span> <span class="dt">\</span></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a>        <span class="at">--only</span> Metrics/AbcSize <span class="dt">\</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a>        <span class="at">--format</span> json <span class="dt">\</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a>        <span class="va">$(</span><span class="fu">git</span> ls-files <span class="kw">|</span> <span class="fu">grep</span> <span class="st">&#39;\.rb$&#39;</span><span class="va">)</span> <span class="dt">\</span></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a>        <span class="kw">|</span> <span class="ex">jq</span> <span class="at">-r</span> <span class="st">&#39;.files[].offenses[].message&#39;</span> <span class="dt">\</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a>        <span class="kw">|</span> <span class="fu">awk</span> <span class="st">&#39;{gsub(/\/0]/, &quot;&quot;, $13); sum += $13} END {print sum}&#39;</span></span></code></pre></div>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>We’ve written about this before:</p>
<ul>
<li><a href="our-technology-stack-and-how-we-got-here.html">Our technology stack, and how we got here</a></li>
<li><a href="technology-stack-transitions-are-hard.html">Technology stack transitions are hard</a></li>
</ul>
<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></li>
<li id="fn2"><p>I use macOS but anything vaguely POSIX-compatible should work.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>Clearly this is a metaphor. In this post I’ll be using
<a href="https://nixos.org">nix</a>, <a href="https://www.gnu.org/software/bash/">bash</a>,
<a href="https://jqlang.github.io/jq/">jq</a>, and
<a href="https://www.gnu.org/software/gawk/">gawk</a>, but if you want to follow along
with <em>actual</em> duct tape and WD-40, be my guest.<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn4"><p>Two weeks ago this number was <code>11574.6</code>!<a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Thu, 22 Aug 2024 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/save-effort-build-a-bash-one-liner.html</guid>
    <dc:creator>Luke Worth</dc:creator>
</item>
<item>
    <title>How We Use Both Process Orchestration and Process Choreography</title>
    <link>https://exploring-better-ways.bellroy.com/how-we-use-both-process-orchestration-and-process-choreography.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>How We Use Both Process Orchestration and Process Choreography</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="https://en.gravatar.com/avatar/fdc0b9bf1c68e3f6824e05631913edc5/"
              alt="Chad Musick profile image"
            />
            <a class="no-underline font-bold" href="/contributors/chad_musick.html">Chad Musick</a>
            <small>2024-08-08</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/data.html">Data and Analytics</a>
          </div>
        </header>
        <section><h2 id="message-passing-in-software">Message passing in software</h2>
<p>At their most atomic, all software processes rely on message passing. Various architectures are constructed or (for those of us who like to anthropomorphize our work) arise naturally around the messengers and the messages to control their flow. In hardware, these messages may be stored in memory registers. In software, these messages are handled in a variety of ways according to the language and its operating model.</p>
<p>In online systems, distributed processes are typical. In an ideal example, you open a browser and come to <a href="https://bellroy.com">Bellroy’s website</a>. We serve you enticing images of our products and show you their features. You pick one or more and order. We accept your order, take payment, and instruct one of our warehouses to send your new carry goods to you.</p>
<p>There are lots of obvious message partners here:</p>
<ul>
<li>User to/from their browser</li>
<li>The user’s browser to/from Bellroy’s website</li>
<li>Bellroy’s website to/from Bellroy’s internal applications</li>
<li>Bellroy’s internal applications to/from the payment service</li>
<li>Bellroy’s internal applications to/from the accounting system</li>
<li>Bellroy’s internal applications to/from the warehouses</li>
</ul>
<p>Three basic models of message passing predominate in distributed systems:</p>
<ol>
<li>Synchronous: The <strong>sender</strong> sends a message and waits for the <strong>recipient</strong> to fully respond</li>
<li>Asynchronous to queue: The <strong>sender</strong> sends a message, which enters a queue and is (maybe) processed later</li>
<li>Asynchronous to subscribers: The <strong>publisher</strong> sends a message, which it then forwards to a set of zero or more <strong>subscriber</strong> processes.</li>
</ol>
<p>In practice, these are often combined. A browser might send a synchronous request to a website, which might synchronously submit an event to an event-stream platform like <a href="https://kafka.apache.org">Apache Kafka</a>, which might acknowledge that message and then publish it to a number of subscribers. It gets complicated. Without some kind of coordination, that complication makes systems hard to reason about and hard to build correctly.</p>
<p>Many different methods can be used to harness the complications of this distributed process without losing the power of the message-passing paradigm. Most such methods can be classified as <strong>orchestration</strong> or <strong>choreography</strong> (or both). <a href="https://en.wikipedia.org/wiki/Orchestration_(computing)">Orchestration</a> is well-suited to centrally directing the actions of systems known to the orchestrator, such as in workflows, and ill-suited to handling reactions from systems unknown to the orchestrator, such as auditing and analytics systems. In contrast, <a href="https://en.wikipedia.org/wiki/Service_choreography">choreography</a> is well-suited to handle reactions from arbitrary sytems but is typically slower than orchestration.</p>
<h2 id="orchestration-centralised-control">Orchestration: Centralised control</h2>
<p>Orchestration acts in a way similar to historical software practices. There may be many different services (or functions), and these may be spread across multiple computers—and for orchestration, distributed systems are typically involved—but a centralised controller calls out to these services as needed to accomplish a specific task, end-to-end. For most purposes, an orchestrated system can be treated as a black box: you put your choices in, you get results or effects out.</p>
<p>There are different theoretical models for orchestration. Among these, the concept of a finite state machine is the most widely implemented. In a finite state machine, only a finite number of “states” are possible, and the machine transitions from one state to another as it runs. Additional information can be carried through the machine, but this information is not typically considered to be part of the state. A variety of open source and proprietary orchestration engines use this basic model for their operation. At Bellroy, we use <a href="https://aws.amazon.com/step-functions/">AWS Step Functions</a> for orchestration of some AWS services and <a href="https://nifi.apache.org">Apache NiFi</a> for much of our data orchestration.</p>
<h2 id="choreography-independent-actors-deciding-how-to-perform">Choreography: Independent actors deciding how to perform</h2>
<p>In contrast with orchestration, choreography does not rely on a centralised understanding of the order in which things will happen. Instead, each component process listens for signals and decides how to respond to those signals; this can include ignoring them.</p>
<p>The granularity of choreography can differ greatly, from pixels in a diagram to the behavior of entire companies. At the most granular level of choreography are cellular automata, which behave by consistent rules that consider only their current state and the state of their neighbors. <a href="https://playgameoflife.com/">Conway’s Game of Life</a> is a standard example of this type of system. Despite the simplicity of Conway’s rules, complex systems can arise from non-local effects of local interactions. In business, orchestration is the typical way for companies to interact with one another; companies make requests but don’t typically dictate how those requests are accomplished or concern themselves with the internal details of their vendors.</p>
<h2 id="where-is-the-boundary">Where is the boundary?</h2>
<p>Orchestration and choreography can look similar in their implementation details for a particular system, but there are important distinctions between them. In a purely orchestrated system, the orchestrator is concerned with all elements of execution, so replacing any element requires the system to be updated. In a purely choreographed system where all reacting systems are also purely choreographed, no work is performed because every actor is simply signalling others.</p>
<p>Nearly all choreography will be started by some kind of orchestration, and some choreographed responses will themselves be orchestrated. The boundary between the two types of control emerges primarily from the degree of control exerted over the exact process.</p>
<p>In the software context, publish–subscribe (pub/sub) systems are often used to define the boundaries between orchestration (direct invocation) and choreography (message publication). The publisher of a message does not <strong>know</strong> how many systems (if any) will respond to the message. The subscriber who receives a message does not <strong>know</strong> how many other subscribers (if any) are also responding to the message.</p>
<p>In the business context, there is often a defined set of steps to follow for a process. This is an orchestrated process. However, the business may pay for an outside vendor to achieve some outcome without caring how the other business does so. From the client perspective, this is a choreographed step.</p>
<h2 id="how-bellroy-uses-both-orchestration-and-choreography">How Bellroy uses both orchestration and choreography</h2>
<p>Bellroy needs to accomplish several distinct but related tasks to effectively serve our current and future customers. Let’s look at how Bellroy does this at high level.</p>
<ol>
<li><p>Serve our website: Primarily choreography</p>
<p>Bellroy’s website relies on a variety of different elements being loaded, such as images, text, scripts, and design specifications. The order in which these are loaded is not important. Bellroy’s servers respond to these requests independently. The browser requests elements as it learns about them, such as reading image URLs from HTML tags, and renders the page when it has enough information to do so.</p></li>
<li><p>Process orders: Primarily orchestration</p>
<p>Our order fulfilment process includes the following steps:</p>
<ul>
<li>Check that stock is available for the shipping address.</li>
<li>Calculate the order total and verify that it matches what was displayed.</li>
<li>Collect payment information in the front-end and relay it to our payment processor; then, record the results (which may take a very long time to come if manual verification is necessary).</li>
<li>Send notices to our warehouses to ship the products.</li>
<li>Record the transaction in our accounting system.</li>
</ul></li>
<li><p>Perform internal processes: Primarily choreography</p>
<ul>
<li>Gather orders from other electronic and physical marketplaces and ship those that require shipping.</li>
<li>Keep track of inventory levels.</li>
<li>Analyze how changes to Bellroy’s website affect sales.</li>
<li>Predict how much inventory to order from suppliers.</li>
</ul></li>
</ol>
<p>When the order of execution is unimportant, choreography allows for <strong>design</strong> efficiency at the cost of <strong>execution</strong> complexity; the same initial signal can result in many different routes for execution, including routes not known at the time of building the thing that generates the signal.</p>
<p>In many ways, orchestration is the micromanagement of processes. These are instructed to execute in a particular order, and the response status and result of each process is tracked with the intent of using the information to inform the next process. This is necessary when the order of execution matters (we don’t want to collect payment if we can’t fulfil an order, for example). Beyond this, it can be very efficient since it does not inherently require asynchronous message passing.</p>
<p>Used together, they <strong>can</strong> provide fast, flexible, reliable service that is easy to audit and expand without refactoring older systems. This requires conscious effort and an understanding of the tradeoffs involved in the choices of system types and boundaries between them.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Thu, 08 Aug 2024 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/how-we-use-both-process-orchestration-and-process-choreography.html</guid>
    <dc:creator>Chad Musick</dc:creator>
</item>
<item>
    <title>Servant on AWS Lambda, and Two New Libraries</title>
    <link>https://exploring-better-ways.bellroy.com/servant-on-aws-lambda-and-two-new-libraries.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Servant on AWS Lambda, and Two New Libraries</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/jack.png"
              alt="Jack Kelly profile image"
            />
            <a class="no-underline font-bold" href="/contributors/jack_kelly.html">Jack Kelly</a>
            <small>2024-07-08</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>In a <a href="/the-history-of-nix-at-bellroy.html">previous post</a>, I mentioned
that we run most of our Haskell code on <a href="https://aws.amazon.com/lambda/">AWS
Lambda</a>. Today, I want to look inside
those binaries, discuss the libraries we use to do “serverless” web
services on AWS, and highlight a few new libraries we’ve recently open
sourced.</p>
<h2 id="serverless-web-services-on-aws">Serverless Web Services on AWS…</h2>
<p>Lambda is AWS’s “function-as-a-service” product. You upload your code
to AWS (either as a zip file or a container image), and when it is
“invoked”, AWS will find some hardware to run it on. Most invocations
are done using an AWS SDK, where you provide a JSON payload and
receive a JSON response.</p>
<p>It’s possible to build “serverless” web services out of Lambda
Functions by integrating your functions with other AWS products. We
currently use <a href="https://aws.amazon.com/api-gateway/">AWS API Gateway</a>
for this. API Gateways have several <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-integration-types.html">integration
types</a>,
but the one we use most often is the <code>AWS_PROXY</code> integration. This
integration has the API Gateway call a Lambda Function to handle the
HTTP request, using JSON representations of the HTTP request and
response.</p>
<h2 id="in-haskell">…in Haskell</h2>
<p>Hackage has a few different “Lambda runtime” packages, but the one we
have settled on is
<a href="https://hackage.haskell.org/package/hal"><code>hal</code></a>. It provides
functions that implement the request/response loop that Lambda
functions are required to perform (requesting the next invocation from
AWS, returning results to AWS, etc.) as well as marshalling types for
common AWS events. Its maintainer has also been a delight to work
with, and we’re grateful for his diligence and receptiveness to
enhancements and bug fixes. For API Gateway, <code>hal</code> provides the
<code>AWS.Lambda.Events.ApiGateway.ProxyRequest</code> and
<code>AWS.Lambda.Events.ApiGateway.ProxyResponse</code> modules, describing the
HTTP request/response JSON structures used by an API Gateway REST API.</p>
<p>To fit <code>hal</code>’s
<a href="https://hackage.haskell.org/package/hal-1.1/docs/AWS-Lambda-Runtime.html#v:mRuntime"><code>mRuntime</code></a>
function and implement our request handler, we need to provide a
function <code>ProxyRequest -&gt; m ProxyResponse</code>. This is morally very
similar to the
<a href="https://hackage.haskell.org/package/wai-3.2.4/docs/Network-Wai.html#t:Application"><code>Application</code></a>
type provided by <a href="https://hackage.haskell.org/package/wai"><code>wai</code></a>, the
standard interface between Haskell web servers and web
frameworks. <code>wai</code> is Haskell’s version of Ruby’s
<a href="https://rubygems.org/gems/rack/versions/1.6.4"><code>rack</code></a> or Python’s
<a href="https://peps.python.org/pep-3333/"><code>wsgi</code></a>: it provides an
<code>Application</code> type for frameworks to provide and servers to consume
and standard types for HTTP requests and responses, which together
form a <a href="https://github.com/oilshell/oil/wiki/Perlis-Thompson-Principle">narrow
waist</a>
between servers and clients.</p>
<p>If we can convert a <code>hal</code> <code>ProxyRequest</code> to a <code>wai</code> <code>Request</code> and a
<code>wai</code> <code>Response</code> to a <code>hal</code> <code>ProxyResponse</code>, we can lift nearly any
<code>wai</code> <code>Application</code> into a Lambda Function. One of our first
open-source Haskell packages,
<a href="https://hackage.haskell.org/package/wai-handler-hal"><code>wai-handler-hal</code></a>,
does exactly this. We have a <a href="https://github.com/bellroy/wai-handler-hal-example">working example on
GitHub</a> if you
want to experiment with it, and we were very pleased to discover that
the <a href="https://zurihac.com/">ZuriHac</a> registration system <a href="https://github.com/bellroy/wai-handler-hal/pull/26#issue-2039813976">moved off a
custom Lambda runtime</a>
to <code>hal</code>+<code>wai-handler-hal</code>.</p>
<h2 id="building-apis-servant">Building APIs: Servant</h2>
<p>Now that we can run standard web application stacks on Lambda, which
one do we use? Since we build a lot of APIs,
<a href="https://hackage.haskell.org/package/servant"><code>servant</code></a> is the
obvious choice. It’s an excellent framework for specifying and
building out APIs, but intermediate Haskellers often struggle to
implement Servant servers using custom monads.</p>
<p>Many application monads just carry around a “context” of read-only
data such as environment variables, and do not do any tricky control
flow. Such monads admit a
<a href="https://hackage.haskell.org/package/unliftio-core-0.2.1.0/docs/Control-Monad-IO-Unlift.html#t:MonadUnliftIO"><code>MonadUnliftIO</code></a>
instance, and Servant APIs built on top of them can be converted to
<code>wai</code> <code>Application</code>s in a generic way. We recently released
<a href="https://hackage.haskell.org/package/unliftio-servant-server"><code>unliftio-servant-server</code></a>,
a package of helpers to do this for both traditional and record-based
Servant APIs.</p>
<h2 id="serving-activeresource-apis">Serving ActiveResource APIs</h2>
<p>Until recently, the majority of Bellroy’s systems were written in
<a href="https://www.ruby-lang.org/en/">Ruby</a>, often on
<a href="https://rubyonrails.org/">Rails</a>. As we’ve migrated more systems to
Haskell, we’ve occasionally found the
<a href="https://github.com/rails/activeresource"><code>activeresource</code></a> library
from Rails to be quite helpful. <code>activeresource</code> is an
object-relational mapper (ORM) for RESTful API endpoints: it
represents individual instances of resources as Ruby objects with a
similar interface to Rails’ database ORM,
<a href="https://guides.rubyonrails.org/active_record_basics.html"><code>activerecord</code></a>. When
the database structures are simple enough (not too many joins), the
similarity between <code>activerecord</code> and <code>activeresource</code> has let us
carve out functionality from our Rails projects without extensive
rewrites.</p>
<p><code>activeresource</code> is a bit particular about the routes and HTTP status
codes that it expects, so we developed
<a href="https://hackage.haskell.org/package/servant-activeresource"><code>servant-activeresource</code></a>
to capture the pattern. By encoding the conventions that
<code>activeresource</code> expects, we are sure to get the Haskell side right
every time.</p>
<h2 id="conclusions">Conclusions</h2>
<p>This basic stack (APIs in Servant, wrapped in <code>wai-handler-hal</code>,
running on AWS Lambda, behind an API Gateway) has proven very effective
for us, and has become the default way we build services at
Bellroy. It’s not a universal answer — some problems are better
solved with traditional virtual machines or other persistent compute,
Application Load Balancers become <a href="https://serverless-training.com/articles/save-money-by-replacing-api-gateway-with-application-load-balancer/">more cost-effective than API
Gateways</a>
at scale, and CloudFront can now use <a href="https://aws.amazon.com/blogs/networking-and-content-delivery/using-amazon-cloudfront-with-aws-lambda-as-origin-to-accelerate-your-web-applications/">Lambda URLs as
origins</a>
— but it’s worked very well for us so far. We’ve been lucky that
there’s so many good libraries in this space on Hackage, and we’re
pleased that we’ve been able to give back with a few of our own.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Mon, 08 Jul 2024 12:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/servant-on-aws-lambda-and-two-new-libraries.html</guid>
    <dc:creator>Jack Kelly</dc:creator>
</item>
<item>
    <title>Using Dhall To Manage GitHub Actions Workflows</title>
    <link>https://exploring-better-ways.bellroy.com/using-dhall-to-manage-github-actions-workflows.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Using Dhall To Manage GitHub Actions Workflows</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="https://avatars.githubusercontent.com/u/137736401?v=4"
              alt="Alvin Tang profile image"
            />
            <a class="no-underline font-bold" href="/contributors/alvin_tang.html">Alvin Tang</a>
            <small>2024-06-04</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>In my previous post, I talked about how we use <a href="https://github.com/features/actions">Github Actions</a>
to automate our workflows. As promised, I will show how we use <a href="https://dhall-lang.org/">Dhall</a> to
manage our GitHub Actions files.</p>
<h2 id="dont-repeat-yourself">Don’t Repeat Yourself</h2>
<p>“Don’t Repeat Yourself” (DRY) is a programming principle that reduces repetition in code.
As we created more actions in a growing number of repositories, we noticed that there were a lot of
repeating steps, jobs, and even whole workflows. Github Actions has features to make workflows DRY such
as composite actions and reusable workflows. However, these are useful only in certain situations:</p>
<ul>
<li><p>Composite actions are useful for running a sequence of steps e.g. <a href="https://www.docker.com/">Docker</a>
setup, login, then fetch an image.</p></li>
<li><p>Reusable workflows are useful for running whole jobs e.g. building and testing a
<a href="https://rubyonrails.org/">Ruby on Rails</a> repository</p></li>
</ul>
<p>Github Actions does not have an easy way to reuse steps or small groups of steps.
Dhall gave us that flexibility.</p>
<h2 id="life-before-dhall">Life before Dhall</h2>
<p>Every time we create a new repository, workflow files are also added. The easiest thing to do is copy
a workflow file from another repository and edit it. Since the workflow has to be specific to the repository,
some steps have to be modified. Eventually, this leads to repetitiveness and consistency problems. The
workflows look similar but not the same. We also encountered template format errors such as misaligned
tabs and missing required keys.</p>
<p>As the number of repositories grows, so do the workflows. We needed a more reliable way of creating new
workflows and maintaining old ones.</p>
<h2 id="enter-the-dhall-configuration-language">Enter the Dhall configuration language</h2>
<p>Dhall is a programmable configuration language. It creates JSON and YAML files with the benefits of
programming principles such as <a href="https://docs.dhall-lang.org/tutorials/Language-Tour.html#types">types</a>,
<a href="https://docs.dhall-lang.org/tutorials/Language-Tour.html#let-expressions">let expressions</a>,
<a href="https://docs.dhall-lang.org/tutorials/Language-Tour.html#file-imports">imports</a>,
and <a href="https://docs.dhall-lang.org/tutorials/Language-Tour.html#functions">functions</a>. I won’t go into
detail about all features of Dhall. The <a href="https://docs.dhall-lang.org/index.html">documentation</a>
provides an overview and some tutorials. In this post, I will just look at some of its
features that help us manage our YAML files.</p>
<h2 id="github-actions-dhall"><code>github-actions-dhall</code></h2>
<p>We did not re-invent the wheel with Dhall and GitHub Actions. There’s already an
<a href="https://github.com/regadas/github-actions-dhall">open-source project for it in GitHub</a>. We started with
that and contributed along the way whenever we thought it was useful.</p>
<p>Let’s start with a simple Dhall file to write a workflow that builds <a href="https://nodejs.org/en">Node.js</a>.
We want to produce the GitHub Actions workflow below:</p>
<pre><code>name: Node.js CI
on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: &quot;actions/checkout@v2&quot;
      - name: Use Node.js
        uses: &quot;actions/setup-node@v2&quot;
        with:
          node-version: &#39;21&#39;
      - name: Install Dependencies
        run: npm install
      - name: Run Build
        run: npm run build</code></pre>
<p>The Dhall file to produce the YAML above:</p>
<pre><code>let GitHubActions = https://raw.githubusercontent.com/regadas/github-actions-dhall/HEAD/package.dhall

let checkoutStep = GitHubActions.Step::{
    , name = Some &quot;Checkout&quot;
    , uses = Some &quot;actions/checkout@v2&quot;
    }

let buildJob = GitHubActions.Job::{
    , runs-on = GitHubActions.RunsOn.Type.`ubuntu-latest`
    , steps = [
      Step,
      GitHubActions.Step::{
        , name = Some &quot;Use Node.js&quot;
        , uses = Some &quot;actions/setup-node@v2&quot;
        , `with` = Some (toMap {
          , node-version = &quot;21&quot;
          })
        },
      GitHubActions.Step::{
        , name = Some &quot;Install Dependencies&quot;
        , run = Some &quot;npm install&quot;
        },
      GitHubActions.Step::{
        , name = Some &quot;Run Build&quot;
        , run = Some &quot;npm run build&quot;
        }
      ]
    }

let workflow = GitHubActions.Workflow::{
    , name = &quot;Node.js CI&quot;
    , on = GitHubActions.On::{
      , push = Some GitHubActions.Push::{
        , branches = Some [&quot;main&quot;]
        }
      , pull_request = Some GitHubActions.PullRequest::{
        , branches = Some [&quot;main&quot;]
        }
      }
    , jobs = toMap { buildJob }
    }

in workflow</code></pre>
<p>In this file, we define several blocks using the <code>let</code> expression. First, we import <code>github-actions-dhall</code>.
<code>github-actions-dhall</code> has defined types for a step, job and workflow. We use Dhall’s
<a href="https://docs.dhall-lang.org/tutorials/Language-Tour.html#record-completion">record completion feature</a>
so we only fill in values that are not default. You can check the repository for the
<a href="https://github.com/regadas/github-actions-dhall/tree/master/defaults">default values</a>.</p>
<p>To produce a YAML file from Dhall, run the <code>dhall-to-yaml</code> executable:</p>
<pre><code>dhall-to-yaml --file workflow1.dhall &gt; workflow1.yaml</code></pre>
<p>You’ll notice that Dhall produces a slightly different YAML file:</p>
<pre><code>jobs:
  buildJob:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: &quot;actions/checkout@v2&quot;
      - name: Use Node.js
        uses: &quot;actions/setup-node@v2&quot;
        with:
          node-version: &#39;21&#39;
      - name: Install Dependencies
        run: npm install
      - name: Run Build
        run: npm run build
name: Node.js CI
on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main</code></pre>
<p>Dhall orders the keys alphabetically. This takes some getting used to but this will work just the same.
YAML doesn’t care about the order of keys.</p>
<p>The Dhall file is still longer than the YAML file, right? Let’s make the workflow a bit more complex by
creating two jobs: one that builds node 21, and one that builds node 20. From the workflow file, we notice
that only one step needs to change, <code>actions/setup-node@v2</code>. We need to change this to use node-version 20.
In a YAML file, this means we copy the whole job then replace the value for that step only. In Dhall, I can
refactor the common steps and reference the corresponding <code>let</code> expressions in the jobs that need it:</p>
<pre><code>let GitHubActions = https://raw.githubusercontent.com/regadas/github-actions-dhall/HEAD/package.dhall

let checkoutStep = GitHubActions.Step::{
    , name = Some &quot;Checkout&quot;
    , uses = Some &quot;actions/checkout@v2&quot;
    }

let npmInstallStep =
      GitHubActions.Step::{
        , name = Some &quot;Install Dependencies&quot;
        , run = Some &quot;npm install&quot;
        }

let npmRunBuildStep =
      GitHubActions.Step::{
        , name = Some &quot;Run Build&quot;
        , run = Some &quot;npm run build&quot;
        }

let buildNode21Job = GitHubActions.Job::{
    , runs-on = GitHubActions.RunsOn.Type.`ubuntu-latest`
    , steps = [
      checkoutStep,
      GitHubActions.Step::{
        , name = Some &quot;Use Node.js&quot;
        , uses = Some &quot;actions/setup-node@v2&quot;
        , `with` = Some (toMap {
          , node-version = &quot;21&quot;
          })
        },
      npmInstallStep,
      npmRunBuildStep
      ]
    }

let buildNode20Job = GitHubActions.Job::{
    , runs-on = GitHubActions.RunsOn.Type.`ubuntu-latest`
    , steps = [
      checkoutStep,
      GitHubActions.Step::{
        , name = Some &quot;Use Node.js&quot;
        , uses = Some &quot;actions/setup-node@v2&quot;
        , `with` = Some (toMap {
          , node-version = &quot;20&quot;
          })
        },
      npmInstallStep,
      npmRunBuildStep
      ]
    }

let workflow = GitHubActions.Workflow::{
    , name = &quot;Node.js CI&quot;
    , on = GitHubActions.On::{
      , push = Some GitHubActions.Push::{
        , branches = Some [&quot;main&quot;]
        }
      , pull_request = Some GitHubActions.PullRequest::{
        , branches = Some [&quot;main&quot;]
        }
      }
    , jobs = toMap { buildNode21Job, buildNode20Job }
    }

in  workflow</code></pre>
<p>The Dhall file is getting bigger. We can utilise the import feature to break this file into multiple smaller files.
Create a <code>steps.dhall</code> file and let’s put all our step definitions there:</p>
<pre><code>let GitHubActions = https://raw.githubusercontent.com/regadas/github-actions-dhall/HEAD/package.dhall

let checkoutStep = GitHubActions.Step::{
    , name = Some &quot;Checkout&quot;
    , uses = Some &quot;actions/checkout@v2&quot;
    }

let npmInstallStep =
      GitHubActions.Step::{
        , name = Some &quot;Install Dependencies&quot;
        , run = Some &quot;npm install&quot;
        }

let npmRunBuildStep =
      GitHubActions.Step::{
        , name = Some &quot;Run Build&quot;
        , run = Some &quot;npm run build&quot;
        }

in { checkoutStep, npmInstallStep, npmRunBuildStep }</code></pre>
<p>The variables inside <code>in { ... }</code> are exported. We will then import the <code>steps.dhall</code> file in our original Dhall file:</p>
<pre><code>let GitHubActions = https://raw.githubusercontent.com/regadas/github-actions-dhall/HEAD/package.dhall

let Steps = ./steps.dhall

let buildNode21Job = GitHubActions.Job::{
    , runs-on = GitHubActions.RunsOn.Type.`ubuntu-latest`
    , steps = [
      Steps.checkoutStep,
      GitHubActions.Step::{
        , name = Some &quot;Use Node.js&quot;
        , uses = Some &quot;actions/setup-node@v2&quot;
        , `with` = Some (toMap {
          , node-version = &quot;21&quot;
          })
        },
      Steps.npmInstallStep,
      Steps.npmRunBuildStep
      ]
    }

let buildNode20Job = GitHubActions.Job::{
    , runs-on = GitHubActions.RunsOn.Type.`ubuntu-latest`
    , steps = [
      Steps.checkoutStep,
      GitHubActions.Step::{
        , name = Some &quot;Use Node.js&quot;
        , uses = Some &quot;actions/setup-node@v2&quot;
        , `with` = Some (toMap {
          , node-version = &quot;20&quot;
          })
        },
      Steps.npmInstallStep,
      Steps.npmRunBuildStep
      ]
    }

let workflow = GitHubActions.Workflow::{
    , name = &quot;Node.js CI&quot;
    , on = GitHubActions.On::{
      , push = Some GitHubActions.Push::{
        , branches = Some [&quot;main&quot;]
        }
      , pull_request = Some GitHubActions.PullRequest::{
        , branches = Some [&quot;main&quot;]
        }
      }
    , jobs = toMap { buildNode21Job, buildNode20Job }
    }

in  workflow</code></pre>
<p>We can do the same exercise for the job expressions to make it even shorter. For now, let’s keep it this way.</p>
<p>Now how about the <code>Use node.js</code> step? They look similar and only differ in the node version. In Dhall, we
can write functions to handle this. We create a function <code>useNodeVersionStep</code> and pass a <code>nodeVersion</code> argument
of type <code>Text</code> and it outputs a <code>GitHubActions.Step.Type</code>. Then we call this function where we want to add a
step, then pass the node version that we’d like to use:</p>
<pre><code>let GitHubActions = https://raw.githubusercontent.com/regadas/github-actions-dhall/HEAD/package.dhall

let Steps = ./steps.dhall

let useNodeVersionStep
    : Text → GitHubActions.Step.Type
    = λ(nodeVersion : Text) →
        GitHubActions.Step::{
        , name = Some &quot;Use Node.js&quot;
        , uses = Some &quot;actions/setup-node@v2&quot;
        , `with` = Some (toMap {
          , node-version = nodeVersion
          })
        }

let buildNode21Job =
    GitHubActions.Job::{
    , runs-on = GitHubActions.RunsOn.Type.`ubuntu-latest`
    , steps = [
      Steps.checkoutStep,
      (useNodeVersionStep &quot;21&quot;),
      Steps.npmInstallStep,
      Steps.npmRunBuildStep
      ]
    }

let buildNode20Job = GitHubActions.Job::{
    , runs-on = GitHubActions.RunsOn.Type.`ubuntu-latest`
    , steps = [
      Steps.checkoutStep,
      (useNodeVersionStep &quot;20&quot;),
      Steps.npmInstallStep,
      Steps.npmRunBuildStep
      ]
    }

let workflow = GitHubActions.Workflow::{
    , name = &quot;Node.js CI&quot;
    , on = GitHubActions.On::{
      , push = Some GitHubActions.Push::{
        , branches = Some [&quot;main&quot;]
        }
      , pull_request = Some GitHubActions.PullRequest::{
        , branches = Some [&quot;main&quot;]
        }
      }
    , jobs = toMap { buildNode21Job, buildNode20Job }
    }

in  workflow</code></pre>
<p>The last three Dhall files that we made produce the same workflow YAML files:</p>
<pre><code>jobs:
  buildNode20Job:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: &quot;actions/checkout@v2&quot;
      - name: Use Node.js
        uses: &quot;actions/setup-node@v2&quot;
        with:
          node-version: &#39;20&#39;
      - name: Install Dependencies
        run: npm install
      - name: Run Build
        run: npm run build
  buildNode21Job:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: &quot;actions/checkout@v2&quot;
      - name: Use Node.js
        uses: &quot;actions/setup-node@v2&quot;
        with:
          node-version: &#39;21&#39;
      - name: Install Dependencies
        run: npm install
      - name: Run Build
        run: npm run build
name: Node.js CI
on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main</code></pre>
<h2 id="how-we-use-dhall">How we use Dhall</h2>
<p>Now we have a way to refactor common steps, jobs, and also create functions. We decided to put all our
workflows in one repository and refactored them to make it more reusable. We have a folder structure like below:</p>
<ul>
<li><code>dhall</code> - contains all Dhall files
<ul>
<li><code>deps</code> - contains dhall dependencies such as github-actions-dhall</li>
<li><code>libs</code> - contains common Dhall definitions like functions and types</li>
<li><code>steps</code> - contains reusable steps or groups of steps</li>
<li><code>jobs</code> - contains reusable jobs</li>
<li><code>composite</code> - composite actions Dhall files with one directory per composite action</li>
<li><code>workflows</code> - workflow Dhall files with one directory for each repository managed</li>
</ul></li>
<li><code>composite</code> - the composite action YAML files with one directory for each composite action</li>
<li><code>workflows</code> - the workflow YAML files with one directory for each repository managed</li>
</ul>
<p>Generating the YAML files from the Dhall files is straightforward. We opted to use a simple Bash script
instead of sophisticated build tools such as <code>make</code> since we don’t need to track dependencies.</p>
<pre><code>#!/usr/bin/env bash
set -eou pipefail

echo -n &quot;Cleanup files before generating...&quot;
rm -rf composite/
rm -rf workflows/
echo &quot;Done!&quot;

echo -n &quot;Generating composite actions...&quot;
for COMPOSITE in $(ls dhall/composite)
do
  COMPOSITE_NAME=${COMPOSITE%.dhall}
  mkdir -p composite/$COMPOSITE_NAME
  dhall-to-yaml --file dhall/composite/$COMPOSITE &gt;&gt; composite/$COMPOSITE_NAME/action.yml
done
echo &quot;Done!&quot;

for REPOSITORY in $(ls dhall/workflows)
do
  REPOSITORY_NAME=${REPOSITORY%.dhall}
  echo -n &quot;Generating workflows for $REPOSITORY_NAME...&quot;
  mkdir -p workflows/$REPOSITORY_NAME
  for WORKFLOW in $(ls -p dhall/workflows/$REPOSITORY/ | grep -v /$)
  do
    dhall-to-yaml --file dhall/workflows/$REPOSITORY_NAME/$WORKFLOW &gt;&gt; workflows/$REPOSITORY_NAME/$WORKFLOW_NAME.yml
  done
  echo &quot;Done!&quot;
done</code></pre>
<p>This will generate all our workflow files from the Dhall files. We then propagate these workflow files
to their respective repositories using what else? GitHub Actions of course! That merits another post in the future.</p>
<h2 id="how-dhall-helped-us">How Dhall helped us</h2>
<p>Dhall has provided us with flexibility and consistency in managing our GitHub Actions workflows.</p>
<ul>
<li>Creating and updating workflows became less trivial because of importing and functions. Reusability of code
means we are maintaining fewer lines of code.</li>
<li>It has helped us make fewer mistakes because of its type-checking. Something we’ve done by accident before in YAML
is to delete a line and end up with completely broken YAML. In the example below, if you only delete the line with <code>c:</code>
you accidentally put <code>d:</code> into <code>a:</code>. This is an easy mistake to make when <code>a:</code> and <code>c:</code> are both multiple pages long.</li>
</ul>
<pre><code>a:
  b: &quot;b&quot;
c:
  d: &quot;d&quot;</code></pre>
<ul>
<li>No more YAML formatting errors since the files are autogenerated. We commit YAML files alongside Dhall files, and mark
them as generated with <code>.gitattributes</code>. This lets us fearlessly refactor because it will show no diffs in the YAML files.</li>
</ul>
<p>If your GitHub Actions files are getting unwieldy, I suggest you start looking into Dhall to make your life easier!</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Tue, 04 Jun 2024 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/using-dhall-to-manage-github-actions-workflows.html</guid>
    <dc:creator>Alvin Tang</dc:creator>
</item>
<item>
    <title>Bellroy Technology Team: 2023 in Review</title>
    <link>https://exploring-better-ways.bellroy.com/bellroy-technology-team-2023-in-review.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Bellroy Technology Team: 2023 in Review</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/mike_ai_avatar.jpeg"
              alt="Mike Webb profile image"
            />
            <a class="no-underline font-bold" href="/contributors/mike_webb.html">Mike Webb</a>
            <small>2024-03-12</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>Our journey through 2023 has been one of significant transformation and achievement. At the heart of
our success is our commitment to <a href="https://exploring-better-ways.bellroy.com/using-shape-up-what-works-what-weve-changed-whats-next.html">Shape
Up</a>
as our software development methodology, alongside a robust roadmap that outlines our long-term
strategic goals and the immediate steps we can take towards each objective. Our roadmap provides us
with a good litmus test when we’re shaping a project - either it moves toward our strategic
objectives, or it signals a time to reassess those objectives. I’m thrilled to present an overview
what our lil’ ol’ team of 13 was able to achieve last year.</p>
<p><em>Managing configuration as code</em> has been best practice in our industry for many years, but I’m
pleased to say that we’ve had great success introducing it to our non-technical configuration. For
our team, this means allowing the rest of the company to directly manage the content and
configuration they contribute to - we just provide the tools and processes that allow them to ship
changes. We made significant strides in migrating more configuration datasets into our git
repository, complemented by the development of internal tools designed to automate configuration
deployment. This includes sophisticated tooling for unit-testing configuration changes. For example,
we run <a href="https://ro-che.info/articles/2017-12-04-golden-tests">golden tests</a> against our discount
calculation rules by running virtual carts through the rules and comparing the results. Our
configuration contributors have developed a battery of tests for our configuration over time,
allowing them to ship changes with confidence. Through improvements to our tooling - and migration
of more configuration into our git repository - we’ve boosted company velocity in product releases
and campaigns without having to provide technical oversight. The company as a whole publishes
content and configuration changes into production 6-10 times a day.</p>
<p>Our dedication to providing <em>world-class tooling support</em> has redefined how content and front-end
code is released and managed on our site. By streamlining our release processes and introducing
user-friendly interfaces, we’ve empowered a broader spectrum of Bellrovians to contribute. For
example, templated content - such as <a href="https://bellroy.com/careers">the job board on bellroy.com</a> - is
now managed using <a href="https://commonmark.org/">Markdown</a> documents and
<a href="https://frontmatter.codes/">FrontMatter</a>, edited by our People &amp; Culture team using <a href="https://github.com/features/codespaces">Github
Codespaces</a>. Our front end developers did a huge amount of
work in 2023 consolidating our front-end codebase and content to use a standardized component
library. Now, anyone can ship great-looking content with much less need for a design review. 15% of
Bellroy crew (outside the Technology Team) regularly make contributions to our website, which is a
big step-up from 2022.</p>
<p>In 2023, Bellroy sent products to customers in 160 countries. Our goal to <em>service our customers and
partners around the globe</em> demanded a system that could handle the complexities of global commerce,
ensuring competitive shipping offerings and compliance with diverse tax requirements. We overhauled
(and in some instances replaced) our legacy systems to handle these tasks, which also made it
practical to integrate this functionality more tightly into more of our sales channels.</p>
<p>Our migration from <a href="https://rubyonrails.org/">Ruby on Rails</a> monoliths to
<a href="https://www.haskell.org/">Haskell</a> services, adhering to the <a href="https://scs-architecture.org/">Self Contained Systems (SCS)
architecture</a>, has enhanced the stability, maintainability, and
cost-effectiveness of our internal systems. We aim to ensure that <em>our technical stack mirrors our
team makeup</em> and that - while having a pager is a necessity in a 24/7/365 business - <em>holding the
pager is a trivial inconvenience shared by all</em>. The reliability of our Haskell code has drastically
reduced out-of-hours incidents, and our move from reserved <a href="https://aws.amazon.com/ec2/">EC2</a>
instances to <a href="https://aws.amazon.com/pm/lambda">Lambda</a> and
<a href="https://aws.amazon.com/dynamodb/">DynamoDB</a> has significantly cut operational costs. We’ve been
able to reduce the size of the EC2 instances we use, and our monthly bills for each of our SCS
services are a fraction of the cost of running an appropriately sized EC2 instance to perform the
same function. On a related note, this migration has also encouraged us to evaluate and start using
other AWS services - we put our first implementation of a <a href="https://aws.amazon.com/step-functions/">Step
Functions</a> workflow into production in 2023. This AWS
service has allowed us to better orchestrate our Lambda functions and has been a great addition to
our toolset.</p>
<p>Our DevOps team has made great progress in enhancing our <em>global continuous integration and
deployment pipeline</em>. By <a href="https://exploring-better-ways.bellroy.com/embracing-automation-with-github-actions.html">leveraging Github
Actions</a>,
<a href="https://exploring-better-ways.bellroy.com/the-history-of-nix-at-bellroy.html">Nix, and Hydra</a>,
we’ve streamlined our development processes, particularly with our Haskell
<a href="https://monorepo.tools/#what-is-a-monorepo">monorepo</a>. The integration with Hydra has been crucial,
ensuring efficient builds and deployments by rebuilding and deploying only what has changed since
the last build/deploy. This approach has minimized CI/CD times, allowing our developers to focus on
building great things instead of handholding changes into production. Adopting Hydra has not been
without challenges - managing builds for multiple architectures and developing expertise has been
difficult - but the benefits have been worth the effort.</p>
<p>In preparing this post and looking at our next slate of projects, it feels like we’re on a really
good path. Our strategic directives still feel right and allow our developers to independently make
principled design decisions. This is one of the benefits we were hoping to get from Shape Up, and
I’m pleased to report that it continues to deliver. If I could wave a magic wand I’d convert all of
our Ruby code to Haskell code tomorrow, but other than that I’m extremely happy with where we are as
a team and as a company with technical capability as one of its core strengths. I look forward to
sharing what we got up to in 2024.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Tue, 12 Mar 2024 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/bellroy-technology-team-2023-in-review.html</guid>
    <dc:creator>Mike Webb</dc:creator>
</item>
<item>
    <title>Using Shape Up - What Works, What We've Changed, What's Next</title>
    <link>https://exploring-better-ways.bellroy.com/using-shape-up-what-works-what-weve-changed-whats-next.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Using Shape Up - What Works, What We've Changed, What's Next</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="https://www.gravatar.com/avatar/d10267cfb1d18dd97917cec8fdf97569d97366310fc3a710e00f0c47b689e2fc"
              alt="Chris D'Aloisio profile image"
            />
            <a class="no-underline font-bold" href="/contributors/chris_daloisio.html">Chris D'Aloisio</a>
            <small>2024-02-09</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><h2 id="introduction">Introduction</h2>
<p>Project management isn’t just about keeping tasks on track to achieve a
predefined outcome; it’s about delivering value to people. That’s where the
<a href="https://basecamp.com/shapeup">Shape Up methodology</a>, pioneered by
<a href="https://basecamp.com/">Basecamp</a>, stands out. It’s not your typical project
management style – it breaks away from the conventional Agile frameworks such
as <a href="https://www.scrum.org/">Scrum</a>, offering a fresh, focused way to steer
projects.</p>
<p>Shape Up consists of six-week, time-boxed cycles, allowing teams to dive deep
into their work without the distractions of ongoing sprints and endless
backlogs. This approach brings a clarity of purpose to each cycle, making it
easier for teams to produce impactful work. It’s a game-changer for those who
want to see real progress without getting bogged down in ceremonies and task
switching.</p>
<p>In this post, we’re going to explore how Shape Up has influenced the Tech
Team’s ability to deliver value. We’ll give you a sneak peek into how we plan
to evolve this approach further, aiming for better project outcomes in the
future.</p>
<h2 id="understanding-the-shape-up-method">Understanding the Shape Up Method</h2>
<p>Shape Up isn’t just a methodology; it’s a shift in mindset. At its heart, Shape
Up champions projects of fixed time and flexible scope. Projects are tackled in
six-week cycles, providing a clear endpoint and fostering a sense of urgency.</p>
<p>Unlike traditional project management that often stretches timelines to fit the
scope, Shape Up flips the script – the scope is adjusted to fit the timeline.
This ensures projects don’t overextend and results are delivered consistently.</p>
<p>The methodology also introduces ‘cool-down’ periods following each cycle. These
two-week spans allow teams to recharge, reflect, and prepare for the upcoming
cycle on their own time. This break from the grind is crucial, preventing
burnout and keeping project approaches and solutions fresh.</p>
<p>Deep work periods are another cornerstone. By reducing meetings and daily
standups common in methodologies like Scrum, Shape Up allows teams to immerse
deeply in their tasks. This undisturbed focus leads to higher quality outputs
and often, breakthrough innovations. It’s all about giving teams the room they
need to think, create, and solve problems without the constant distraction of
administrative overhead.</p>
<p>In Shape Up, autonomy is key. Teams are trusted to figure out the best way to
tackle their projects. This empowerment not only boosts morale but also leads
to more inventive and tailored solutions.</p>
<h2 id="customizing-shape-up-to-fit-our-needs">Customizing Shape Up to Fit Our Needs</h2>
<p>Embracing Shape Up doesn’t mean rigidly sticking to its every rule. It’s about
making the methodology work for you. At Bellroy, we’ve tailored Shape Up to
better align with our dynamic needs, proving its adaptability and
effectiveness.</p>
<p>One significant change we made is the introduction of a separate “roaming”
team. Operating within the six-week cycles, this team has an allocated
programme of work, but is deliberately left with capacity to deal with
high-value, time-sensitive tasks that come up at short notice. This flexibility
allows us to respond swiftly to urgent needs without disrupting the flow of
ongoing projects.</p>
<p>This customization has significantly improved our responsiveness to other
teams’ demands and schedules. The Tech Team, for instance, isn’t confined to a
single product at Bellroy. We provide core services across the business, often
in response to objectives set by various teams throughout the year. Other teams
within Bellroy, such as our Logistics team, have to be able to respond to
changing circumstances like <a href="https://www.bbc.com/news/world-middle-east-56505413">closed shipping
lanes</a> with short or no
notice and sometimes require our support to do so. The “roaming” team model
enables us to address these needs promptly while maintaining our commitment to
the Shape Up cycles for larger projects.</p>
<p>Additionally, we’ve incorporated more robust feedback loops within each cycle.
Regular check-ins, albeit less frequent than in traditional methodologies,
ensure that everyone stays aligned on goals and progress.</p>
<p>During check-ins, we have explicit cues to ensure that we are not focusing
solely on technical objectives. We ask ourselves if we are on track to
delivering the value that stakeholders are expecting, and that we do not leave
things in an unfinished state at the end of the project.</p>
<p>These adaptations have allowed us to maintain the essence of Shape Up – focused
work periods, autonomy, and regular shipping – while tweaking it to suit our
unique workflow and company culture.</p>
<h2 id="impact-on-productivity-and-project-outcomes">Impact on Productivity and Project Outcomes</h2>
<p>The introduction of Shape Up in the Tech Team wasn’t just a procedural change;
it brought a fundamental shift in how we approach productivity and overall
project value. The flexibility in scope within fixed timelines has resulted in
a more dynamic and responsive work environment, allowing us to pivot
mid-project if required to deliver value where it matters most.</p>
<p>Perhaps the clearest impact has been on project completion rates. The
six-week cycles, with their clear boundaries and goals, have led to a higher
rate of on-time project deliveries. This improvement in delivery has cascaded
across the organisation, improving our overall operational efficiency.</p>
<p>The cool-down periods have also played a vital role in maintaining a
sustainable work pace. These breaks have become a time for reflection and
learning, contributing to continuous improvement in our processes and
approaches.</p>
<p>In essence, Shape Up has not just reshaped our project timelines; it has
reinvigorated our teams and redefined our benchmarks for success.</p>
<h2 id="future-plans-and-improvements">Future Plans and Improvements</h2>
<p>As we look ahead, the journey with Shape Up is far from over. Our plan is to
further refine our approach, especially in capacity planning across the
business.</p>
<p>We believe that a deeper understanding and application of Shape Up’s principle
of fixed time, variable scope can significantly enhance cross-team project
planning and resource management.</p>
<p>We’re also exploring ways to integrate more collaborative tools and techniques
within the Shape Up framework. Our goal is to enhance communication and
alignment among teams, ensuring that every cycle is as effective as possible.</p>
<p>Our experience with Shape Up has been transformative. It has not only improved
how we manage projects but also how we view productivity and team dynamics. As
we continue to evolve and adapt this methodology, we remain committed to
pushing the boundaries of what’s possible in project management for our team
and the rest of the business.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Fri, 09 Feb 2024 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/using-shape-up-what-works-what-weve-changed-whats-next.html</guid>
    <dc:creator>Chris D'Aloisio</dc:creator>
</item>
<item>
    <title>The History of Nix at Bellroy</title>
    <link>https://exploring-better-ways.bellroy.com/the-history-of-nix-at-bellroy.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>The History of Nix at Bellroy</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/jack.png"
              alt="Jack Kelly profile image"
            />
            <a class="no-underline font-bold" href="/contributors/jack_kelly.html">Jack Kelly</a>
            <small>2024-01-24</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>Bellroy relies heavily on <a href="https://nixos.org">Nix</a> as an important
part of our developer tooling. It provides us with reproducible
environments for developer shells and CI runs, as well as a build
environment for our statically linked Haskell code. Our tech team
works in a moderately conservative Haskell dialect, so this level of
Nix dependence might seem surprising and incongruent. In this post, I
will explain how and why our Nix usage evolved in the way that it did,
and point out useful tricks and tools at each stage of adoption.</p>
<h2 id="phase-1-developer-shells-via-shellnix">Phase 1: Developer Shells via <code>shell.nix</code></h2>
<p>Nix has an intimidating learning curve, but most of this comes from
writing Nix expressions. Developers can be easily taught to use
Nix-based infrastructure once it’s been set up. Our first use of Nix
was writing <code>shell.nix</code> files for use with
<a href="http://ghedam.at/15978/an-introduction-to-nix-shell"><code>nix-shell</code></a>. Nix
uses these files to create reproducible development environments
containing correct versions of tools like <code>ruby</code>, <code>ghc</code>, etc.,
depending on the project. This is a great way to get started because
it doesn’t ask developers to radically change their workflows, and
allows them to trial Nix at their own pace. There are some subtleties
to be aware of when setting up these shell expressions.</p>
<ul>
<li><p>For true reproducibility, you need to store a reference to the
version of <a href="https://github/nixos/nixpkgs"><code>nixpkgs</code></a> used in each
project’s source control. This is called “pinning nixpkgs”. We
initially did this using the
<a href="https://github.com/nmattia/niv/"><code>niv</code></a> tool, and later by using
<a href="https://nix-tutorial.gitlabpages.inria.fr/nix-tutorial/flakes.html">Nix flakes</a>.</p></li>
<li><p>As we have several developers using macOS, we pinned <code>nixpkgs</code>
commits from <code>nixpkgs-*-darwin</code> branches. We found that this
improved the cache hit rate for our macOS-using developers, and
reduced the amount of software they had to build locally.</p></li>
<li><p>For Ruby and npm projects, we found it too difficult to capture all
of their dependencies as Nix expressions. Packages in private
repositories and on private package registries were the biggest
challenge here, as many <code>foo2nix</code> tools only support public package
repositories. As a workaround, our shells provide the language
runtime (e.g., <code>ruby</code>) and its packaging tool (e.g., <code>bundler</code>), but
leave fetching language-level dependencies to that language’s
tool. We have found this to be a reasonable trade-off between
correctness and practicality. The
<a href="https://nixos.org/manual/nix/stable/command-ref/conf-file.html#conf-access-tokens"><code>access-tokens</code></a>
setting in modern versions of Nix might help us, if we revisit this.</p></li>
</ul>
<h2 id="phase-2-building-haskell-deployment-packages-using-haskellnix">Phase 2: Building Haskell Deployment Packages using <code>haskell.nix</code></h2>
<p>We were comfortable just using Nix for developer shells for a
fairly long time, until a confluence of several constraints forced us
into a more elaborate Nix setup.</p>
<p>Most of our Haskell code is deployed to <a href="https://aws.amazon.com/lambda/">AWS
Lambda</a>. To build binaries for this
environment, we originally used the
<a href="https://hub.docker.com/r/lambci/lambda"><code>lambci/lambda</code></a> Docker
container to build in an environment close to what AWS provides at
runtime. This ceased to be viable once we started using <a href="https://kafka.apache.org">Apache
Kafka</a>: the Haskell client we use
(<a href="https://hackage.haskell.org/package/hw-kafka-client"><code>hw-kafka-client</code></a>)
binds to <a href="https://github.com/confluentinc/librdkafka/"><code>librdkafka</code></a>,
which is not provided by the AWS runtime environment. Instead of
wrangling third-party RPM repositories or <a href="https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html">Lambda
Layers</a>,
we used the excellent
<a href="https://github.com/input-output-hk/haskell.nix"><code>haskell.nix</code></a>
framework to build statically linked,
<a href="https://upx.github.io/">UPX-compressed</a> deployment packages. We
published <a href="https://github.com/bellroy/wai-handler-hal-example/blob/master/flake.nix">example Nix code
code</a>
which does this, as part of our
<a href="https://hackage.haskell.org/package/wai-handler-hal"><code>wai-handler-hal</code></a>
project.</p>
<h2 id="phase-3-private-binary-cache-using-amazon-s3-and-github-actions">Phase 3: Private Binary Cache using Amazon S3 and GitHub Actions</h2>
<p>Nix + <code>haskell.nix</code> was a reliable way to generate deployment packages
for our Haskell services, but even after <a href="https://input-output-hk.github.io/haskell.nix/tutorials/getting-started#setting-up-the-binary-cache">adding IOG’s binary
cache</a>
we would often have a lot of cache misses, leading to very long build
times (particularly on macOS). It was time to bite the bullet and set
up our own private cache. Nix links against the <a href="https://aws.amazon.com/sdk-for-cpp/">AWS SDK for
C++</a> and can use <a href="https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-help-stores#s3-binary-cache-store">S3-compatible
object
stores</a>
as binary caches, so an S3 Bucket was an obvious place to store our
derivations. We needed a way to populate the cache, and weren’t ready
to tackle Nix-native solutions like
<a href="https://github.com/nixos/hydra">Hydra</a>, so we built out a caching
workflow using <a href="https://github.com/features/actions">GitHub Actions’</a>
hosted Linux and macOS runners. Behind this simple idea are a lot of
details worth getting right, so we’ve tried to capture as many of them
here as we possibly can.</p>
<h3 id="setting-up-the-bucket">Setting up the Bucket</h3>
<ul>
<li><p>The bucket is just a normal S3 bucket. Because Nix uses the S3 API,
we can <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html">block all public
access</a>
and leave <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html">website
hosting</a>
turned off.</p></li>
<li><p>It might be worth creating the bucket in the region closest to most
of your developers.</p></li>
<li><p>It is generally the case that many derivations stop being relevant
shortly after they’ve been built.</p>
<ul>
<li><p>It might be worth considering an <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-transition-general-considerations.html">S3 Lifecycle
Configuration</a>
to migrate old derivations from the “Standard” <a href="https://aws.amazon.com/s3/storage-classes/">Storage
Class</a> to “Standard
— Infrequent Access” and possibly even “Glacier Instant
Retrieval”. Be careful of increased retrieval charges when using
these storage classes.</p></li>
<li><p><a href="https://aws.amazon.com/s3/storage-classes/intelligent-tiering/">S3 Intelligent
Tiering</a>
might also be worth considering. Be careful of its automation
charges.</p></li>
<li><p>It is possible to use a Lifecycle Configuration to delete very old
derivations, but this can confuse the cache of Nix clients. It
might also confuse Hydra (which keeps records of which derivations
it has built).</p></li>
</ul></li>
<li><p>The Nix manual provides example AWS Identity and Access Management
(AWS IAM) Policy Documents <a href="https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-help-stores#s3-binary-cache-store">for read-only and read/write
access</a>
to an S3 Bucket. Actually providing credentials to Nix that have
these permissions can be tricky, due to constraints imposed by Nix:</p>
<ul>
<li><p>We cannot use regular credentials to assume a more restricted
role, because the C++ SDK that Nix uses <a href="https://github.com/aws/aws-sdk-cpp/issues/150#issuecomment-538064354">does not support
<code>assume_role</code> entries in
<code>~/.aws/config</code></a>.</p></li>
<li><p>The Nix daemon runs as the <code>root</code> user, so we need to configure
credentials in <code>root</code>’s home directory, and cannot use interactive
ways of providing credentials.</p></li>
<li><p>In the AWS cloud, Nix should be able to access credentials in the
normal way (e.g., <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html">EC2 Instance
Profiles</a>).</p></li>
<li><p>Nix on non-cloud machines (e.g., developer laptops) is more
difficult. We are basically forced into using long-lived
<code>aws_access_key_id</code> and <code>aws_secret_access_key</code> pairs. This is not
best practice, so we don’t want these keypairs to be able to do
too much. We recommend creating entirely separate IAM Users that
can only access the cache bucket, and creating a separate User for
each developer or server that needs access. Automating key
rotation or setting up the <a href="https://docs.aws.amazon.com/config/latest/developerguide/access-keys-rotated.html"><code>access-keys-rotated</code>
rule</a>
in <a href="https://aws.amazon.com/config/">AWS Config</a> can help ensure
that keys are rotated regularly.</p></li>
<li><p>The GitHub Actions Workflow that populates the cache will assume
an AWS IAM Role with permissions to read and write the cache
bucket. We don’t create an IAM User for the workflow, because
GitHub Actions supports <a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect">OpenID
Connect</a>
and provides a guide for <a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services">configuring OpenID Connect between
GitHub and
AWS</a>.</p></li>
</ul></li>
</ul>
<h3 id="setting-up-keys">Setting up Keys</h3>
<p>Nix uses public/private key pairs to know which derivations to trust:
our builder will sign derivations with the private key before
uploading them to S3, and clients will know to trust the corresponding
public key.</p>
<ul>
<li><p>We generated a cache key pair following the recommendation <a href="https://nixos.org/manual/nix/stable/advanced-topics/post-build-hook.html#set-up-a-signing-key">in the
Nix
manual</a>:</p>
<pre><code>$ nix-store --generate-binary-cache-key example-nix-cache-1 key.private key.public</code></pre></li>
<li><p>The private key was stored as as a <a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets">GitHub Actions
Secret</a>.</p></li>
<li><p>The public key was set in the <code>nixConfig</code> setting of our flakes,
which means that it applies to only our repositories. This speeds
up cache checking for other builds, as Nix clients will only check
our bucket when it makes sense:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">description</span> <span class="op">=</span> <span class="st">&quot;A flake&quot;</span><span class="op">;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">inputs</span> <span class="op">=</span> <span class="op">.</span>..;</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  outputs = <span class="op">.</span>..;</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>  nixConfig = <span class="op">{</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>    <span class="va">extra-substituters</span> <span class="op">=</span> <span class="op">[</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>      <span class="st">&quot;s3://example-nix-cache?profile=bellroy&quot;</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>      <span class="st">&quot;https://cache.iog.io&quot;</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>    <span class="op">];</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>    <span class="va">extra-trusted-public-keys</span> <span class="op">=</span> <span class="op">[</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>      <span class="st">&quot;example-nix-cache-1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=&quot;</span></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>      <span class="st">&quot;hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=&quot;</span></span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>    <span class="op">];</span></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div></li>
</ul>
<h3 id="setting-up-clients">Setting up Clients</h3>
<p>For multi-user Nix installations (the default), these AWS keys need to
be loaded by the user running the Nix daemon (by default, this is
<code>root</code>). These can be set by running commands like:</p>
<pre><code>sudo -H aws configure --profile bellroy set aws_access_key_id AKYOURACCESSKEY
sudo -H aws configure --profile bellroy set aws_secret_access_key YOURSECRETKEY</code></pre>
<p>macOS updates tend to remove files in <code>~root</code>, including AWS config
files. One way to permanently provide credentials to the Nix daemon is
(<a href="https://github.com/NixOS/nix/issues/2161#issuecomment-1610403519">thanks
<code>@lrworth</code></a>):</p>
<ol>
<li>Create AWS config and credential files in <code>/etc/nix/aws/config</code> and
<code>/etc/nix/aws/credentials</code>.</li>
<li>Edit <code>/Library/LaunchDaemons/org.nixos.nix-daemon.plist</code>, adding
the following lines under <code>&lt;key&gt;EnvironmentVariables&lt;/key&gt;</code>:</li>
</ol>
<div class="sourceCode" id="cb4"><pre class="sourceCode xml"><code class="sourceCode xml"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>&lt;<span class="kw">key</span>&gt;AWS_CONFIG_FILE&lt;/<span class="kw">key</span>&gt;</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>&lt;<span class="kw">string</span>&gt;/etc/nix/aws/config&lt;/<span class="kw">string</span>&gt;</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>&lt;<span class="kw">key</span>&gt;AWS_SHARED_CREDENTIALS_FILE&lt;/<span class="kw">key</span>&gt;</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>&lt;<span class="kw">string</span>&gt;/etc/nix/aws/credentials&lt;/<span class="kw">string</span>&gt;</span></code></pre></div>
<ol>
<li>Run <code>sudo -i sh -c 'launchctl remove org.nixos.nix-daemon &amp;&amp; launchctl load /Library/LaunchDaemons/org.nixos.nix-daemon.plist'</code>
to restart the Nix daemon.</li>
</ol>
<h3 id="setting-up-the-workflow">Setting up the Workflow</h3>
<p>Here is a YAML description of a sample workflow, derived from the
workflow that we previously used to update our cache:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="fu">name</span><span class="kw">:</span><span class="at"> Populate nix shell cache</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="fu">on</span><span class="kw">:</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="at">  </span><span class="fu">schedule</span><span class="kw">:</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">cron</span><span class="kw">:</span><span class="at"> </span><span class="st">&quot;0 0 * * 0&quot;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="at">  </span><span class="fu">workflow_dispatch</span><span class="kw">:</span><span class="at"> </span><span class="kw">{}</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="fu">jobs</span><span class="kw">:</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="at">  </span><span class="fu">populate-cache</span><span class="kw">:</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="at">    </span><span class="fu">strategy</span><span class="kw">:</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="fu">fail-fast</span><span class="kw">:</span><span class="at"> </span><span class="ch">false</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="fu">matrix</span><span class="kw">:</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="at">        </span><span class="fu">os</span><span class="kw">:</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="at">          </span><span class="kw">-</span><span class="at"> ubuntu-latest</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="at">          </span><span class="kw">-</span><span class="at"> macos-latest</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a><span class="at">    </span><span class="fu">runs-on</span><span class="kw">:</span><span class="at"> </span><span class="st">&quot;${{ matrix.os }}&quot;</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="at">    </span><span class="fu">steps</span><span class="kw">:</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> </span><span class="st">&quot;actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8&quot;</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> </span><span class="st">&quot;aws-actions/configure-aws-credentials&quot;</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a><span class="at">        </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a><span class="at">          </span><span class="fu">aws-region</span><span class="kw">:</span><span class="at"> </span><span class="st">&quot;${{ env.AWS_REGION }}&quot;</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a><span class="at">          </span><span class="fu">role-to-assume</span><span class="kw">:</span><span class="at"> </span><span class="st">&quot;${{ secrets.AWS_OIDC_ROLE_ARN }}&quot;</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> </span><span class="st">&quot;cachix/install-nix-action@daddc62a2e67d1decb56e028c9fa68344b9b7c2a&quot;</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a><span class="at">        </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a><span class="fu">          extra_nix_config</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a>            post-build-hook = /etc/nix/upload-to-cache.sh</span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a>            substituters = https://cache.nixos.org/ https://cache.iog.io s3://example-nix-cache</span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a>            trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= example-nix-cache-1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a><span class="at">          </span><span class="fu">install_url</span><span class="kw">:</span><span class="at"> https://releases.nixos.org/nix/nix-2.7.0/install</span></span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a><span class="at">          </span><span class="fu">nix_path</span><span class="kw">:</span><span class="at"> nixpkgs=channel:nixpkgs-22.11-darwin</span></span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Set up nix signing key</span></span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a><span class="at">        </span><span class="fu">run</span><span class="kw">:</span><span class="at"> </span><span class="st">&quot;echo ${{ secrets.NIX_CACHE_NIX_SIGNING_KEY }} | sudo tee /etc/nix/example-nix-cache.private &gt; /dev/null&quot;</span></span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Set up post-build hook</span></span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a><span class="fu">        run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a>          sudo tee /etc/nix/upload-to-cache.sh &lt;&lt;EOF &gt; /dev/null</span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a>          #!/bin/sh</span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a>          set -eu</span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a>          set -f # disable globbing</span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a>          export IFS=&#39; &#39;</span>
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a>          echo &quot;Uploading paths&quot; \$OUT_PATHS</span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a>          exec $(which nix) copy --to &#39;s3://example-nix-cache?region=wherever&amp;secret-key=/etc/nix/example-nix-cache.private&amp;compression=zstd&amp;parallel-compression=true&#39; \$OUT_PATHS</span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a>          EOF</span>
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a>          sudo chmod u+x /etc/nix/upload-to-cache.sh</span>
<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Restart nix-daemon</span></span>
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a><span class="fu">        run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb5-46"><a href="#cb5-46" aria-hidden="true" tabindex="-1"></a>          case $RUNNER_OS in</span>
<span id="cb5-47"><a href="#cb5-47" aria-hidden="true" tabindex="-1"></a>            Linux) sudo systemctl restart nix-daemon.service ;;</span>
<span id="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a>            macOS) sudo launchctl kickstart -k system/org.nixos.nix-daemon ;;</span>
<span id="cb5-49"><a href="#cb5-49" aria-hidden="true" tabindex="-1"></a>          esac</span>
<span id="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Install nix-build-uncached</span></span>
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a><span class="fu">        run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb5-52"><a href="#cb5-52" aria-hidden="true" tabindex="-1"></a>          nix-env -iE &#39;_: import (builtins.fetchTarball {</span>
<span id="cb5-53"><a href="#cb5-53" aria-hidden="true" tabindex="-1"></a>            url = &quot;https://github.com/Mic92/nix-build-uncached/archive/77fe5c8c4c5c7a1fa3f9baa042474b98f2456652.tar.gz&quot;;</span>
<span id="cb5-54"><a href="#cb5-54" aria-hidden="true" tabindex="-1"></a>            sha256 = &quot;sha256:04hqiw3rhz01qqyz2x1q14aml1ifk3m97pldf4v5vhd5hg73k1zn&quot;;</span>
<span id="cb5-55"><a href="#cb5-55" aria-hidden="true" tabindex="-1"></a>          }) {}&#39;</span>
<span id="cb5-56"><a href="#cb5-56" aria-hidden="true" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Build shells</span></span>
<span id="cb5-57"><a href="#cb5-57" aria-hidden="true" tabindex="-1"></a><span class="fu">        run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb5-58"><a href="#cb5-58" aria-hidden="true" tabindex="-1"></a>          nix-build-uncached -build-flags &#39;-L --keep-going&#39; -E &#39;(import ./.).devShells.${builtins.currentSystem}&#39;</span></code></pre></div>
<p>As with the rest of this process, the basic idea is simple (run the
workflow to build all derivations required by our development shells),
but the devil is in the details:</p>
<ul>
<li><p>A Nix <a href="https://nixos.org/manual/nix/stable/advanced-topics/post-build-hook.html">post-build
hook</a>
to sign and upload any derivations we build.</p></li>
<li><p>We used
<a href="https://github.com/Mic92/nix-build-uncached"><code>nix-build-uncached</code></a>
(now deprecated) to build only the derivations that we could not
find in S3, preventing lots of redundant
downloads. <code>nix-build-uncached</code> does not support flakes, so we
invoked the build through a <code>default.nix</code> which uses
<a href="https://github.com/edolstra/flake-compat/"><code>flake-compat</code></a>.</p>
<p>The deprecation notice in <code>nix-build-uncached</code>’s <code>README.md</code>
suggests more modern alternatives:</p>
<ul>
<li><p><a href="https://github.com/Mic92/nix-fast-build"><code>nix-fast-build</code></a> has a
<code>--skip-cached</code> flag. A <a href="https://github.com/NixOS/nix/issues/3946#issuecomment-1351758621">comment on Nix issue #3946</a>
says that <code>nix-eval-jobs</code> (which powers <code>nix-fast-build</code>) can be
problematic when lots of import-from-derivation (IFD) is required,
as in <code>haskell.nix</code>;</p></li>
<li><p>The comments on <a href="https://github.com/NixOS/nix/issues/3946">Nix issue #3946</a> suggest that <code>nix build --store $remote_store --builders auto</code> might (eventually?)
work.</p></li>
</ul></li>
<li><p>If we were doing this again, we’d probably consider Determinate
Systems’ <a href="https://determinate.systems/posts/magic-nix-cache">Magic Nix
Cache</a> to
evaluate Nix expressions more quickly, before the build begins.</p></li>
<li><p>We ran the workflow weekly as a trade-off between cache freshness and
billable minutes, and enabled manual workflow dispatch for when we
upgraded GHC versions or major packages.</p></li>
<li><p>We use <a href="https://facebook.github.io/zstd/">Zstandard</a> compression
when we upload to S3, because it’s very light on CPU time and we
found that XZ was very slow on large derivations.</p></li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>You don’t have to adopt Nix all at once to get good value out of
it. Simple development shells prevent a lot of headaches and tend to
have good cache hit rates, which means that it’s fine to delay private
caching until much later. Our initial <code>shell.nix</code> served us well for
over a year before we started adding more sophisticated tooling, and
we only did that because we were forced. Our moves to <code>haskell.nix</code>
and GitHub-Actions-based caching were made in response to genuine needs,
and we learned as we went. We did eventually move to a Hydra-based CI
system, but that’s a story for another time.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Wed, 24 Jan 2024 12:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/the-history-of-nix-at-bellroy.html</guid>
    <dc:creator>Jack Kelly</dc:creator>
</item>
<item>
    <title>Embracing automation with GitHub Actions</title>
    <link>https://exploring-better-ways.bellroy.com/embracing-automation-with-github-actions.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Embracing automation with GitHub Actions</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="https://avatars.githubusercontent.com/u/137736401?v=4"
              alt="Alvin Tang profile image"
            />
            <a class="no-underline font-bold" href="/contributors/alvin_tang.html">Alvin Tang</a>
            <small>2023-11-30</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p><a href="https://github.com/features/actions">Actions</a> is a feature of <a href="https://github.com">GitHub</a> for
automating software workflows. It includes
<a href="https://resources.github.com/ci-cd/">Continuous Integration and Delivery (CI/CD)</a> so it allows us
to build, test, and deploy our code. In this post, I’ll discuss some of the things we love about it
and what we think it can improve on.</p>
<h2 id="things-we-love-about-actions">Things we love about Actions</h2>
<h3 id="easy-integration-with-github">Easy integration with GitHub</h3>
<p>One major reason for using Actions is its built on top of GitHub. If a repository is already in
GitHub, using Actions is as simple as pushing a workflow file in the correct directory. Workflow
files are YAML files defining the steps to be done when an event is triggered. There is no need to
run a separate application and/or server since GitHub provides it already.</p>
<p>In our organization, we have non-technical users working on content and configuration files. We
moved these files into a git repository so they are version controlled. We taught users git basics
including pushing commits and creating a pull request for their changes. With this move, we were
also able to add workflows to make everyone’s lives easier. The workflows do several things like
running tests, generating files, and verifying protected content; several workflows
build updated assets and deploy them to the correct environment. The workflows also provide
feedback by generating PR comments and <a href="https://slack.com/">Slack</a> messages. All of these workflows
run automatically or can be manually triggered. Over time, our users have come to
understand what happens in these workflows and how to respond to different types of failures.
And they are able to do this all within the familiar surrounds of Github.</p>
<h3 id="event-triggers">Event triggers</h3>
<p>CI/CD workflows are usually triggered by events. Actions provide
<a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows">a wide array of event triggers</a>.
The <code>push</code> and <code>pull request</code> events are the most common; you can use these to trigger builds and run tests on a repository.</p>
<p>We’ve found uses for other event triggers such as:</p>
<ul>
<li>building a package for a draft release, triggered by the <code>release</code> event</li>
<li>running a workflow from an external script, triggered by the <code>repository_dispatch</code> event</li>
<li>merging a pull request upon approval, triggered by the <code>pull_request_review</code> event</li>
</ul>
<h3 id="marketplace">Marketplace</h3>
<p>There are workflow actions that are common to many repositories. These include actions like checking
out a repository, building a <a href="https://www.docker.com/">Docker</a> image, or setting up tools like <a href="https://nodejs.org/">node</a>.
Actions has a <a href="https://github.com/marketplace?type=actions">marketplace</a> containing actions that
other developers have published. GitHub also provides its own actions such as <code>checkout</code>,
<code>upload-artifact</code>, or <code>download-artifact</code>. Aside from GitHub, a lot of third-party providers
have published actions to support their products. If we want to integrate actions with other tools
like Docker or Slack, we try searching the marketplace first!</p>
<p>Here are some of the third-party actions we use:</p>
<ul>
<li><a href="https://github.com/actions/checkout">github/checkout</a> - This action checks out a repository so we
can work with its contents. This allows you to checkout multiple repositories. For example, we
have a workflow that works with two other repositories. We can check out each of those repositories
in their own directories so we can work with the files inside them.</li>
<li><a href="https://github.com/docker/build-push-action">docker/build-push-action</a> - We use Docker containers
for building <a href="https://github.com/features/codespaces">GitHub codespaces</a>. This Docker action builds
a Docker image for our codespaces and pushes it to a registry.</li>
<li><a href="https://github.com/slackapi/slack-github-action">slackapi/slack-github-action</a> - We post workflow
updates in Slack. This action allows us to send messages to Slack. It has formatting and even
support for threaded messages!</li>
<li><a href="https://github.com/cachix/install-nix-action">cachix/install-nix-action</a> - We use
<a href="https://nixos.org/">Nix</a> in most of our repositories. This action installs Nix in GitHub-hosted
runners so we can run code in our defined Nix environment.</li>
<li><a href="https://github.com/aws-actions/configure-aws-credentials">aws-actions/configure-aws-credentials</a> -
This action configures a runner to access <a href="https://aws.amazon.com/">AWS</a>. We use this to generate
<a href="https://openid.net/developers/how-connect-works/">OIDC</a> tokens so workflows can access AWS resources
during build, test, and deployment.</li>
</ul>
<h3 id="composite-actions-and-reusable-workflows">Composite actions and reusable workflows</h3>
<p>A few months into our Actions journey, workflows were getting repetitive. We started exploring how
to <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a> them up.
<a href="https://docs.github.com/en/actions/creating-actions/creating-a-composite-action">Composite actions</a>
and <a href="https://docs.github.com/en/actions/using-workflows/reusing-workflows">reusable workflows</a>
address this problem. These allow us to group repetitive steps or jobs and call them in one step,
instead of writing the steps all over again.</p>
<p>A sample composite action we have is called <code>slack-update</code>. This is a wrapper around
<a href="https://github.com/slackapi/slack-github-action">slackapi/slack-github-action</a>. We have predefined
messages for certain events such as deployment success or failure messages. Using a composite
action for this keeps our messaging consistent across different repositories. Here is a code
snippet of this composite action:</p>
<pre><code>description: Pinned slackapi/slack-github-action. Posts a message to the specified slack channel.
using: composite
name: slack update
inputs:
  authors:
    description: authors of the change
    required: true
  branch:
    description: branch name to put in messages
    required: true
  channel-id:
    description: slack channel ID where message will be posted
    required: true
  message-type:
    description: &quot;type of message to be posted. Options are: `build-start`, `build-fail`, `build-success`, `open-pr`&quot;
    required: true
  pr-url:
    default: &#39;&#39;
  ...
runs:
  steps:
    - if: &quot;contains( inputs.message-type, &#39;open-pr&#39; ) &amp;&amp; inputs.branch == &#39;master&#39;&quot;
      name: Slack Notifications - Create PR payload
      run: |
        echo &quot;PAYLOAD={\&quot;text\&quot;:\&quot;:*${{ input.authors }}* has opened a PR: ${{ inputs.pr-url }}\&quot;}&quot; &gt;&gt; $GITHUB_ENV
      shell: bash
    - env:
        SLACK_BOT_TOKEN: &quot;${{ inputs.slack-bot-token }}&quot;
      name: Slack - Notifications - Post to slack
      uses: &quot;slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117&quot; # see &quot;Managing workflows&quot; for more on why we use a SHA here
      with:
        channel-id: &quot;${{ inputs.channel-id }}&quot;
        payload: &quot;${{ env.PAYLOAD }}&quot;</code></pre>
<p>And then we call the composite action as a step in workflows:</p>
<pre><code>steps:
...
- if: &quot;github.ref == &#39;refs/heads/master&#39;&quot;
  name: Post pull request link to Slack
  uses: &quot;bellroy/workflows/composite/slack-update@master&quot;
  with:
    branch: &quot;${{ env.BRANCH }}&quot;
    channel-id: &quot;${{ env.SLACK_CHANNEL_ID }}&quot;
    message-type: open-pr
    pr-url: &quot;${{ env.PR_URL }}&quot;
    slack-bot-token: &quot;${{ secrets.SLACK_TOKEN }}&quot;</code></pre>
<p>Other examples of our composite actions:</p>
<ul>
<li><code>get-tool</code> - We build internal tools that are released through GitHub. These tools are used within
our workflows so they have to be retrieved for the workflow to run successfully. We wrote an action to get the latest
release of a tool, or we can retrieve a specific version.</li>
<li><code>install-nix</code> - This is a wrapper around <a href="https://github.com/cachix/install-nix-action">cachix/install-nix-action</a>
to customize Nix installations. We have predefined setups for different access levels to our private
Nix cache.</li>
</ul>
<h3 id="self-hosted-runners">Self-hosted runners</h3>
<p>Self-hosted runners can be set up to run actions in our own server. We do this when workflows need
access to protected resources. Our self-hosted runners run within our private network. Aside from
that, we also customize the runner by adding the tools it needs to do its job. For example, one of
our workflows need to produce messages to a <a href="https://kafka.apache.org/">Kafka</a> topic in
<a href="https://aws.amazon.com/msk/">AWS Managed Streaming for Apache Kafka (MSK)</a>. We have provisioned a
self-hosted runner in the same network as the MSK cluster and installed
(kcat)[<a href="https://github.com/edenhill/kcat" class="uri">https://github.com/edenhill/kcat</a>] as the Kafka client.</p>
<h3 id="if-all-else-fails-use-bash-scripts">If all else fails, use bash scripts</h3>
<p>The ability to use bash scripts in the workflows provides endless possibilities. We have scripts
for output formatting, file manipulations, running tests, cleaning up after builds, etc. Combining
this with a controlled environment (such as a self-hosted runner), we can do most of the automation we
need.</p>
<h2 id="things-we-dont-love-so-much-about-actions">Things we don’t love so much about Actions</h2>
<h3 id="developing-and-testing-workflows">Developing and testing workflows</h3>
<p>The process of developing and testing actions is not the best. Some event triggers need the workflow
change to be on the default branch to be executed. There are ways to work around this such as adding
an event that can trigger even if it’s not on the default branch. But this still has limitations if
we want to test the actual event, instead of the steps.</p>
<p>Workflows can also take a long time to run. A minor mistake such as a typographical
error can cost us minutes before we get feedback from actions. This gets compounded if testing in
self-hosted runners since the actions are queued.</p>
<h3 id="managing-workflows">Managing workflows</h3>
<p>As the number of repositories grows, so does the number of workflows. Although there are mechanisms
to DRY them up, it’s better to manage these workflows in one place. One benefit of this approach is
that it makes managing our third-party actions much easier. As best practice, we pin third-party
actions to commit SHAs (which can’t change), rather than release tags (which can), so we don’t get
surprised by updates that can break our workflows or get exposed to <a href="https://snyk.io/blog/npm-security-preventing-supply-chain-attacks/">supply chain attacks</a>.
This means we have to update these SHAs every so often. This becomes painful when there are a lot of
repositories involved.</p>
<hr />
<p>Overall, GitHub Actions is a great tool for automation and CI/CD. If you are already using GitHub,
it provides easy integration and a wide variety of actions to use. It is also versatile enough to
support custom actions specific to your needs.</p>
<p>Next time, I will discuss how we use <a href="https://github.com/dhall-lang/dhall-lang">Dhall</a> to manage
our workflow files for Github Actions.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Thu, 30 Nov 2023 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/embracing-automation-with-github-actions.html</guid>
    <dc:creator>Alvin Tang</dc:creator>
</item>
<item>
    <title>Celebrating the release of Amazonka 2.0</title>
    <link>https://exploring-better-ways.bellroy.com/celebrating-the-release-of-amazonka-2-0.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Celebrating the release of Amazonka 2.0</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/mike_ai_avatar.jpeg"
              alt="Mike Webb profile image"
            />
            <a class="no-underline font-bold" href="/contributors/mike_webb.html">Mike Webb</a>
            <small>2023-09-12</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>For businesses which directly depend upon open source software, investing developer time
and expertise can yield more tangible and enduring benefits than monetary sponsorship alone,
fundamentally strengthening the technological infrastructure upon which your business’ success
depends. Today, we at Bellroy are pleased to discuss our role in the
<a href="https://hackage.haskell.org/package/amazonka-2.0">the 2.0 release of Amazonka</a>
— a vital Haskell package providing bindings for the AWS (Amazon Web Services) infrastructure. This
package stands as <em>the</em> primary tool in the Haskell ecosystem addressing these needs, and it is
extremely satisfying to see a new major release on <a href="https://hackage.haskell.org">Hackage</a>.</p>
<p>The Amazonka 2.0 release included a significant contribution from our dedicated staff engineer, Jack
Kelly. <a href="http://jackkelly.name/blog/archives/2023/08/30/the_road_to_amazonka_2_0/index.html">Jack devoted considerable time and effort</a>
to the release, during and beyond his regular working hours, to ensure its successful delivery. His
commitment is testament to the passion we share for open-source development and our desire to
contribute back to this vibrant community.</p>
<p>We rely upon Amazonka in many of our production systems. Upon noticing that the project could
greatly benefit from having a dedicated full-time developer, we supported Jack by allocating more
than six weeks of full-time work to the project. Now that it’s released, we eagerly look forward to
seeing the ripple effect this release (and our other contributions) will have in the Haskell community.
It is our hope that Haskell ecosystem contributions such as these will inspire more developers to look
favourably at Haskell when considering technologies for their next project. We also hope it
encourages more companies to invest in open-source contributions and support the ecosystems
in which they operate.</p>
<p>At Bellroy, we have always held a deep respect for open-source software. It significantly benefits
our own endeavors, and underpins a significant portion of today’s digital infrastructure.
<a href="/our-technology-stack-and-how-we-got-here.html">Our journey</a>
from a proprietary e-commerce platform to a completely bespoke one would be unimaginable if
not for the availability of open-source frameworks like Magento and Rails. We view
contributing to the open-source community not as a nice-to-have, but as an obligation in
keeping with our aim of using business as a force for good.</p>
<p>When we create technology that we believe could be useful to a wider audience — and
it isn’t a source of competitive advantage — we generally consider
open-sourcing it. Prior to adopting <a href="/technology-stack-transitions-are-hard.html">Haskell as our primary development language</a>,
we open-sourced multiple Ruby and Elm packages, such as:</p>
<ul>
<li><a href="https://github.com/bellroy/dry-monads-sorbet">dry-monads-sorbet</a> <a href="https://sorbet.org/">Sorbet</a> types for the
<a href="https://dry-rb.org/gems/dry-monads/1.3/">dry-monads</a> Ruby gem.</li>
<li><a href="https://github.com/bellroy/elm-email">elm-email</a> Parse email addresses safely in Elm.</li>
<li><a href="https://github.com/bellroy/elm-embed-youtube">elm-embed-youtube</a> A simple way of embedding a
Youtube player using an iframe, in Elm.</li>
<li><a href="https://github.com/bellroy/elm-imgix">elm-imgix</a> A wrapper around <a href="https://imgix.com/">Imgix</a>, in Elm.</li>
<li><a href="https://github.com/bellroy/elm-infinite-gallery">elm-infinite-gallery</a> A simple gallery that
supports infinite scrolling, in Elm.</li>
<li><a href="https://github.com/samuelgiles/rspec-sorbet">rspec-sorbet</a>* A Ruby gem which makes it easier to use Sorbet &amp; <a href="https://rspec.info/">RSpec</a> together.</li>
<li><a href="https://github.com/samuelgiles/sorbet-struct-comparable">sorbet-struct-comparable</a>* Add equality testing to classes which inherit from Sorbet’s <a href="https://sorbet.org/docs/tstruct"><code>T::Struct</code></a> class.</li>
</ul>
<p>* A former Bellroy employee is now the primary maintainer of these packages.</p>
<p>Amazonka joins a growing list of open-source Haskell (and other FP) libraries we’ve contributed to,
including:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/hal">hal</a> A runtime environment for Haskell applications
running on AWS Lambda.</li>
<li><a href="https://github.com/bellroy/haskell-cached-io">haskell-cached-io</a> Cache a single IO action (we’ve
taken over maintainership of the <a href="https://github.com/glasserc/haskell-cached-info">original repository</a>)</li>
<li><a href="https://github.com/regadas/github-actions-dhall">github-actions-dhall</a> Typecheck, template and
modularize your Github Action definitions with Dhall.</li>
</ul>
<p>The following libraries are examples of where we saw a package-sized gap in the ecosystem, and
decided to fill it ourselves.</p>
<ul>
<li><a href="https://github.com/bellroy/aws-arn">aws-arn</a> Type and optics for manipulating Amazon Resource Names (ARNs)</li>
<li><a href="https://github.com/bellroy/timeline">timeline</a> A library for handling data that changes over time</li>
<li><a href="https://github.com/bellroy/wai-handler-hal">wai-handler-hal</a> Lets you run a WAI application as
the backend Lambda of an AWS API Gateway REST API</li>
</ul>
<p>We generally try to contribute upstream features and fixes first, even if doing so requires a little more
initial digging. This approach is better for everyone in the long run: it reduces our maintenance burden,
helps other people with their problems, and shores up the long-term viability of the Haskell ecosystem.</p>
<p>Bellroy’s mission is to become global leaders in carry, and we work towards this by “using business
as a force for good” and “helping the world, and our crew, flourish”. This successful release
reaffirms our “upstream-first” model of open-source contribution, and to release additional
open-source packages — watch this space.</p>
<hr />
<p>We’d like to say a big “thank you” to <a href="https://www.brendanhay.nz/">Brendan Hay</a> for creating Amazonka,
and everyone who has contributed to the above-listed projects over the years.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Tue, 12 Sep 2023 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/celebrating-the-release-of-amazonka-2-0.html</guid>
    <dc:creator>Mike Webb</dc:creator>
</item>
<item>
    <title>Humans rarely need to make business decisions on real-time data</title>
    <link>https://exploring-better-ways.bellroy.com/humans-rarely-need-to-make-business-decisions-on-real-time-data.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Humans rarely need to make business decisions on real-time data</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="https://en.gravatar.com/avatar/fdc0b9bf1c68e3f6824e05631913edc5/"
              alt="Chad Musick profile image"
            />
            <a class="no-underline font-bold" href="/contributors/chad_musick.html">Chad Musick</a>
            <small>2023-08-15</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/data.html">Data and Analytics</a>
          </div>
        </header>
        <section><p>All other things being equal, fresher data are better data. However, it is almost never the case that all other things are equal.</p>
<ul>
<li>Many data warehouses charge differently and more for streaming updates than for batch updates (see <a href="https://cloud.google.com/bigquery/pricing/#data_ingestion_pricing">BigQuery ingestion pricing</a> as an example)</li>
<li>Updates to some data may necessitate updates to other data, leading to a cascade of updates</li>
<li>Fresh data may be unreliable in isolation (see <a href="/assembling-reliable-records-from-semi-reliable-systems.html">“how to handle piecemeal data”</a> for handling issues such as referential integrity)</li>
<li>Initial trends may be misleading, such as in <a href="https://mcfunley.com/whom-the-gods-would-destroy-they-first-give-real-time-analytics">real-time web analytics</a></li>
</ul>
<p>Every decision made at every company has some agent making that decision. Perhaps it is an artifical intelligence making it, or perhaps a deterministic business process, or sometimes, a human.
Fundamentally, every decision is made at some level by a human, even if they decide to delegate it to a computer system. See Dr. Marshall’s book <a href="https://www.brandeismarshall.com/book">Data Conscience</a> about the effects such delegation can have and how to consider them in better ways.</p>
<p>When deciding how fresh your data need to be, the key question to ask is this: “How quickly would new data change the actions of the decision-making agent?” This clarifies things immediately. As examples:</p>
<ul>
<li>A fraud-detection system should prevent fraudulent orders, but in order to do so it must act fast enough to prevent the order from completing</li>
<li>Website personalisation should happen quickly enough to render the website</li>
<li>Stock-level updates should happen quickly enough to prevent selling stock that can’t be fulfilled</li>
</ul>
<p>Strategic decisions – the kind that often rely on reports, dashboards, and other visualisations made by analysts using <a href="https://www.gartner.com/reviews/market/analytics-business-intelligence-platforms">Business Intelligence</a> software – are typically <em>not</em> made in seconds. Could new data arrive in the next five minutes that would change such decisions? Obviously, just as much as noting that we must get halfway to a destination before we can get to that destination. The chance of such information arriving is small, and waiting “just in case” for perfect certainty will result in an infinite wait. However, we are not flummoxed by <a href="https://iep.utm.edu/zenos-paradoxes/">Zeno’s paradoxes</a> here, though they are some of the oldest recorded objections to real-time analytics.</p>
<p>Strategic decisions, then, can be made as effectively from old but representative data as from fresh but representative data. The important aspect of the data is that they are representative, not that they are arbitrarily new. Website conversion rates from two weeks ago may still be representative if nothing material has changed, and sales data from ten minutes ago may be unrepresentative if an item has gone out of stock. See our previous post <a href="/keeping-on-call-calm.html">“Keeping on-call calm”</a> for applying this principle to operational issues.</p>
<p>Since all other things are rarely equal, decide the speed of data updates according to how those data will be used. Then, communicate the expected latencies in a place accessible to everyone who will rely on the data. Set alarms for critical conditions. Your decisions will be just as good, but your data/compute bills will be lower.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Tue, 15 Aug 2023 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/humans-rarely-need-to-make-business-decisions-on-real-time-data.html</guid>
    <dc:creator>Chad Musick</dc:creator>
</item>
<item>
    <title>Technology stack transitions are hard</title>
    <link>https://exploring-better-ways.bellroy.com/technology-stack-transitions-are-hard.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Technology stack transitions are hard</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/mike_ai_avatar.jpeg"
              alt="Mike Webb profile image"
            />
            <a class="no-underline font-bold" href="/contributors/mike_webb.html">Mike Webb</a>
            <small>2023-07-15</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>In today’s rapidly evolving technological landscape, businesses often face the challenge of
transitioning between different technology stacks. In 2019, we made the difficult decision to
<a href="/our-technology-stack-and-how-we-got-here.html">move away from using Ruby and switch to Haskell</a>
as our primary stack. We have found this process to be complex and demanding, particularly when
dealing with legacy systems. Retaining skilled developers who can maintain knowledge of both old and
new stacks has been crucial to our transition. We’ve focused on making incremental progress,
delivering tangible value at each stage. In this blog post, we will delve into the intricacies of
managing the transition between technology stacks, addressing the importance of retaining talent,
maximizing value, and effectively handling “leftover” code.</p>
<p>In a transition like this your legacy stack will inevitably become viewed as “lesser” and, once this
happens, it is difficult to retain developers capable of maintaining it. Once we had made the
decision to move to <a href="https://www.haskell.org/">Haskell</a>, we lost several developers who were
committed to <a href="https://www.ruby-lang.org/en/">Ruby</a> and did not see a
transition to Haskell as their preferred career path. We believed that preserving knowledge of the
legacy codebase was vital for a smooth transition. We identified developers that were willing to
learn to develop in the new stack and ensured they were well-supported in their learning journey. We
ran introductory workshops, book clubs and mentoring sessions, and assigned developers to projects
building basic features in the new stack. This was an effective strategy for mitigating the risks
associated with the transition and ensured continuity.</p>
<p>In a business like ours where software is not the “product” but the business is critically reliant
upon it, we need to transition in safe increments and produce tangible value with each increment.
In some instances the act of transitioning alone delivers sufficient value because the new stack
is a better fit. With Haskell, we have been able to demonstrate a significant reduction in technical
problems and outages as we migrate parts of our systems. In other instances we need to wait
until a change is required in the legacy system and port that functionality in the process of
delivering the change. We try to find ways to add value during this process. For example, we have
many systems that use Domain Specific Language (DSL)-based configuration rules and Haskell is
fantastic for DSLs. We’ve been able to re-use a common DSL for configuration across these systems,
and by doing so, kept our internal-user interfaces consistent and the DSLs more powerful with each
new release. Adding the ability to configure a system using this common DSL has been a great selling
point for transitioning legacy modules.</p>
<p>A natural consequence of an incremental transition strategy is that it will take a long time before
our transition is completed. Bellroy is roughly 3 years into this transition and, looking at the trend
in our Ruby line count, we’re probably about 2 years away from completing it. For all of
that time, we need to keep our dependencies up to date, fix the odd bug in the legacy code, and
support the growth of the business. Given the way things have proceeded so far, we have found that
Haskell offers significant improvements in reusability, stability and maintainability. The
cumulative benefits of those improvements will mean that we can successfully complete the transition
before the legacy codebase becomes too much of a burden.</p>
<p>Even with careful planning and execution, as we approach the end of the transition we’ll have a
substantial amount of “leftover” legacy code. This is code that is critical to
ongoing operations, but is difficult to improve or add value to during the transition to the new
stack. Migrating this code may not directly contribute to Bellroy’s business objectives, making it
challenging to allocate resources for the migration. However, leaving the code in its current state
can have negative implications for future maintenance and scalability. Quantifying these intangible
costs can help with making the case to allocate resources to migrate that code.
At Bellroy, we have built a project evaluation engine (in Haskell, naturally) that lets us
estimate the probabilistic <a href="https://www.investopedia.com/terms/n/npv.asp">Net Present Value (NPV)</a>
of a proposed project using textual descriptions of future cash flows. We use this engine to model
the opportunity cost of maintaining legacy code (as opposed to building new features) and the risk
of retaining legacy systems with a dwindling amount of specialised expertise in the team.</p>
<p>Now if you’ll excuse me - I need to go and think about which part of our systems we’ll supercharge
next…</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Sat, 15 Jul 2023 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/technology-stack-transitions-are-hard.html</guid>
    <dc:creator>Mike Webb</dc:creator>
</item>
<item>
    <title>Getting Things Done, with Asana</title>
    <link>https://exploring-better-ways.bellroy.com/getting-things-done-with-asana.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Getting Things Done, with Asana</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/mike_ai_avatar.jpeg"
              alt="Mike Webb profile image"
            />
            <a class="no-underline font-bold" href="/contributors/mike_webb.html">Mike Webb</a>
            <small>2023-05-01</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>In May of 2023 I wrote a series of posts in our internal company newsletter, “The Owlet”, introducing
the “Getting Things Done” (GTD) system and how it can be applied in our task management tool, Asana.
The feedback I got from these posts was positive and broadly applicable across the many
functional teams within Bellroy. I figured if it had such an effect on such a diverse bunch of
people, it was likely worth sharing with the world. Enjoy!</p>
<h2 id="introducing-getting-things-done">Introducing “Getting Things Done”</h2>
<p><a href="https://gettingthingsdone.com/">GTD is a time management and productivity system created by David Allen</a>.
The system’s core idea is to capture all of the tasks that clutter our minds and to organise them in
a structured way that allows for efficient and stress-free execution.</p>
<p>The system consists of five basic steps:</p>
<ol>
<li>Capture: <strong>capture</strong> all of the tasks that come to mind. This can be done by writing them down in a
notebook or using a digital tool such as a to-do list app.</li>
<li>Clarify: Once everything has been captured, <strong>clarify</strong> what each item actually
means and whether it requires action. This involves determining whether an item is actionable,
and if so, what specific action is needed.</li>
<li>Organise: Once all items have been clarified, they need to be <strong>organised</strong> into categories such as
“Next”, “Blocked” or “Someday”. This step is important for ensuring that tasks
are categorised/prioritised appropriately and that nothing falls through the cracks.</li>
<li>Reflect: regularly review all of the tasks that have been captured and organised, making any
necessary updates or adjustments. <strong>Reflect</strong> on how your process is working and update that, too.</li>
<li>Engage: The final step is to actually <strong>engage</strong> the process, and do the work!</li>
</ol>
<p>I’ve been using the GTD system for several years now in both my personal and professional life.
I’ve found it to be extremely effective. It’s helped me to stay organised and stay focused on the
tasks that are most important. For my personal task list I use <a href="https://www.nirvanahq.com/">Nirvana</a>,
a tool purpose-built for GTD. At work, I use <a href="https://asana.com">Asana</a>. While Asana is not a pure GTD tool,
you can easily implement a GTD system within it.</p>
<p>In the following article, I’ll be sharing some tips on how to implement the GTD system using Asana.</p>
<h2 id="step-1---the-capture-step">Step 1 - the “Capture” step</h2>
<p>The first step of the GTD productivity system is to “Capture” all of
your tasks in a trusted system. Asana is a powerful tool that can help you do just that.</p>
<p>For this system to work for you, you need to have confidence that all of your tasks are captured in
Asana. You need to go through all of your current task lists (including your mental ones) and add
them to Asana. You may require an hour or two to do this, so set aside an appropriate amount of time.
It’s worth it!</p>
<p>The “My Tasks” project will be the place where you can quickly add any new tasks that
come to mind. You can add new tasks to your “My Tasks” project by clicking the + button in the top
bar on any Asana page and selecting “Add task.” This will open a popup window, where you can
describe the task and optionally assign it to a project.</p>
<p>You can also use the <a href="https://asana.com/guide/get-started/begin/asana-mobile-app">Asana mobile app</a>
to capture tasks and ideas on-the-go. Whether you’re waiting in line at the grocery store or sitting
on the bus, you can quickly add a task to your “My Tasks”.</p>
<p>The <a href="https://asana.com/apps/slack">Asana Slack integration</a> allows you to turn Slack messages
straight into Asana tasks, with a link back to your Slack conversation. This is useful when you want
to revisit the conversation without being distracted from your current focus.</p>
<p>Asana’s <a href="https://asana.com/apps/chrome">Chrome browser extension</a> is another powerful tool that can
help you capture tasks and ideas quickly. With the extension installed, you can add tasks to your
“My Tasks” directly from your browser. For example, if you come across an interesting article that you
want to read later, you can quickly add it to your “My Tasks”.</p>
<p>Finally, it’s important to review your “My Tasks” regularly. I’ll talk more about best practices for
this later. For now, set aside time each day to review your “Inbox” and your
“My Tasks” board. I recommend doing this at the start of each day - I usually set aside the first 15
minutes of the day to review these and plan my day. If you know you’re going to spend this time each
day, you start to get less distracted by things appearing in “Recently Assigned” and “Inbox” during
the workday.</p>
<h2 id="step-2---the-clarify-step">Step 2 - the “Clarify” step</h2>
<p>The second step of the GTD productivity system is the “Clarify” step. In
this step, you review all of the tasks you “Captured” in the previous step and determine
whether they require action. This should be incorporated into a daily “My Tasks” and “Inbox” review
process.</p>
<p>For each item in your “Inbox” or “Recently assigned” section of your “My Tasks” project, identify
whether a task is actionable or not. If the task is not actionable, it may be a reference item or
something that needs to be filed for future reference - you might move it into whichever system you
use for reference information (I use <a href="https://obsidian.md/">Obsidian</a> for this purpose;
<a href="https://evernote.com/">Evernote</a> and <a href="https://keep.google.com/">Google Keep</a> are also good tools
for storing snippets of information). If the task <em>is</em> actionable, determine the specific next
action required to move it forward. For example, if the task is “write an article”, the next action
may be “create an outline for the article”.</p>
<p>Use Asana subtasks to lay out the bite-size tasks required to achieve the objective. Pro-tip: you
might want to assign both the parent task and the subtasks to yourself so that you can prioritise
subtasks independently.</p>
<p>For each task, determine the outcome you want to achieve and what context is required to achieve it.
Determining the outcome will help you determine whether the task is actually necessary and whether
it is aligned with your goals. Can the task be completed in your current context? For example, if
you need to make a phone call to complete the task, you may need to be in a quiet place where you
can concentrate. If you’re not in the right context, you may need to defer the task to a later time.
Specifying context is also useful for batching tasks that require similar context.</p>
<p>Finally, for any tasks that can and should be delegated to someone else, delegate them during your
daily review.</p>
<h2 id="step-3---the-organise-step">Step 3 - the “Organise” step</h2>
<p>The third step of the GTD productivity system is the “Organise” step. In this
step, you organise your “My Tasks” into a system that makes sense to you. Asana has multiple
mechanisms for effectively organising your tasks.</p>
<h3 id="using-asana-sections">Using Asana sections</h3>
<p>One of the simplest and most effective organising mechanisms you can put in place is to use Asana
rules to create due-date driven sections of your “My Tasks” project. With these rules in place, once
you put a due date on a task you can be confident it will be surfaced to you at the right time.</p>
<p>My “My Tasks” board has five sections (in order):</p>
<ul>
<li><strong>Recently assigned</strong> - tasks recently assigned to me by myself and others</li>
<li><strong>Blocked</strong> - tasks waiting on other tasks/people</li>
<li><strong>Today</strong> - my daily plan</li>
<li><strong>Next</strong> - tasks due in the next week</li>
<li><strong>Someday</strong> - everything else</li>
</ul>
<p>Here are the rules I have set up on my “My Tasks” project:</p>
<ol>
<li>Move tasks due today to the “Today” section.</li>
<li>Move tasks due in one day to the “Next” section.</li>
<li>Move tasks due in three days to the “Next” section.</li>
<li>Move tasks due in one week to the “Next” section.</li>
</ol>
<p>These last three rules are a bit of a hack, but they work well for me. Asana doesn’t have a way of
saying “move tasks due between one and seven days from now into this section”, so I’ve had to use
multiple rules to make sure everything gets caught.</p>
<h3 id="using-tags">Using tags</h3>
<p>Asana has general-purpose tagging included in all projects by default. You can use tags to
categorise your tasks by just about any criteria you can think of. For example, you might use tags
to organise tasks by:</p>
<ul>
<li>Effort (low, medium, high)</li>
<li>Impact (low, medium, high)</li>
<li>Priority (low, medium, high)</li>
<li>Context (required location e.g. “office”, required equipment e.g. “sketchpad”)</li>
</ul>
<p>… and then use your categorisations to plan your day. For example, “if I don’t have any
high priority tasks to do today while I’m in the office, I’ll schedule work on medium priority tasks
that require the office”.</p>
<h3 id="using-custom-fields">Using custom fields</h3>
<p>The premium tier of Asana includes the ability to <a href="https://asana.com/guide/help/premium/custom-fields">create custom fields</a>.
Custom fields are great for when you want a more finely tuned instrument for categorising or
measuring your tasks in order to organise them. For example, you might add a custom field to include
an estimate of how many minutes a task will take and use that to plan your day. Alternatively, you
can create a custom currency field to estimate the financial benefit of a task and use that as a
prioritisation guide.</p>
<h3 id="use-what-works-for-you">Use what works for you!</h3>
<p>For me, just diligently doing my daily review and having the due-date driven sections of “My Tasks”
is enough to keep me organised. However, in other Tech Team projects we use a combination of tags
and custom columns to prioritise tasks for the team. The important thing is to experiment and find
what works for you. In the next step, you’ll incorporate reflection into your daily review process
and use it to continually improve.</p>
<h2 id="step-4---the-reflect-step">Step 4 - the “Reflect” step</h2>
<p>The “Reflect” step is the fourth step of the GTD productivity system. In this
step, you review and reflect on your tasks and process to ensure that they are still relevant and
aligned with your goals. Setting aside regular time to review your “My
Tasks” and “Inbox” is critical to your ongoing GTD success.</p>
<h3 id="every-day">Every day</h3>
<p>I start each day with a 15 minute review, during which I complete the following steps:</p>
<ol>
<li>Review my “Inbox”. Is there anything that requires action from me? Create tasks for those actions
if they don’t already exist.</li>
<li>Switch to “My Tasks” and review “Recently assigned” and <strong>do</strong> (anything two minutes or less),
<strong>delegate</strong> (re-assign) or <strong>defer</strong> (set a due date and move to the appropriate section)
everything in that section.</li>
<li>Review “Blocked” and see if the tasks in that section are still in fact blocked. If they are not,
move them to the appropriate section. If they are, see if there is anything I can do to unblock
them (sometimes a gentle poke is all that’s needed).</li>
<li>Plan my day - review “Today” and “Next” and put tasks in priority order. Check my calendar and
double check whether my plan is practical given the time I have available outside of meetings.
Reflect on the previous day and whether I under- or over-estimated the time I needed for each
task that I had planned to do.</li>
<li>(If time) Review “Someday” and check that the tasks in those sections are still
relevant and categorised appropriately.</li>
</ol>
<p>Here’s another representation of a similar process, from <a href="https://www.samuelthomasdavies.com/book-summaries/business/getting-things-done/">Sam T. Davies’ summary of Getting Things Done</a> (which is well worth a read).</p>
<p><img src="/images/Getting-Things-Done-Workflow-Chart.webp" alt="Getting Things Done Workflow Chart by Sam T. Davies" /></p>
<h3 id="every-week">Every week</h3>
<p>It’s important to see what you have accomplished and celebrate your wins! Doing this gives you a
lovely little dopamine hit that keeps you motivated and focused on your goals.</p>
<p>In Asana, you can filter your “My Tasks” by “Completed” to see what you have completed in the last
week. I use this view every Friday when the Tech Team does our weekly demos, both to celebrate the
work I’ve done and to pick something cool and/or interesting to show off to the team.</p>
<h2 id="step-5---the-engage-step">Step 5 - the “Engage” step</h2>
<p>The “Engage” step is the fifth and final step of the GTD productivity system.
In this step, you use your GTD system in Asana to do the work! This article has laid out,
broadly, all the tools you need to Get Things Done.</p>
<ol>
<li><strong>Capture</strong> tasks in Asana as soon as you learn about them. Don’t stress too much about
documenting every aspect - just record enough information for you to know what to do with it
during your daily review.</li>
<li><strong>Clarify</strong> the task in your daily review. Determine whether there’s a clear action you can take.
Where possible, update the title and description so that someone else could easily pick it up and
do it. And if it can be delegated, delegate it.</li>
<li><strong>Organise</strong> your tasks using a system that makes sense to you. Use due dates, tags, sections…
whatever you find most effective.</li>
<li><strong>Reflect</strong> on your performance and your system each day. Are you managing to stick to your plan?
If not, why not? You might need to try several organisational approaches before you find the
right one. Be kind to yourself - there will be days where you grossly overestimate or
underestimate your capacity or the work. Try to get better calibrated on what is realistic.</li>
<li><strong>Engage</strong> with the GTD philosophy. Look for the best next action to take from all of your tasks.
Feel better about the tasks you are consciously choosing to do, and the tasks you are consciously
choosing not to do.</li>
</ol>
<p>My subjective experience of prioritising and executing work in this way is that I am less distracted
and more satisfied with my work at the end of each day. I feel less reactive and more proactive.
Sometimes important things show up and demand your immediate attention, and that’s fine. That’s the
nature of working in a fast-paced, high-impact environment. However, I find that I am able to
make the decision to devote my immediate attention more mindfully. I’m able to quickly judge the
importance of the task against my daily plan and be confident that I’m not missing something
important.</p>
<p>In <a href="https://www.amazon.com/Your-Brain-Work-Strategies-Distraction/dp/0061771295">David Rock’s excellent book, “Your Brain At Work”</a>
(<a href="https://www.youtube.com/watch?v=MwUbLjdTYmA">9m video summary of core message</a>),
the author describes task prioritisation as one of the most cognitively demanding activities the
human mind engages in. He also states that the best time to perform this activity is at the start of
your day, when mental energy levels are high. Having a disciplined practice of reviewing your tasks
and planning your day each morning is a great way of aligning your activities for your energy levels.</p>
<p>Good luck, and let’s Get Things Done!</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Mon, 01 May 2023 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/getting-things-done-with-asana.html</guid>
    <dc:creator>Mike Webb</dc:creator>
</item>
<item>
    <title>Keeping on-call calm</title>
    <link>https://exploring-better-ways.bellroy.com/keeping-on-call-calm.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Keeping on-call calm</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="https://avatars.githubusercontent.com/u/2180709"
              alt="Cat Truscott profile image"
            />
            <a class="no-underline font-bold" href="/contributors/cat_truscott.html">Cat Truscott</a>
            <small>2023-03-20</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>Being on-call can be stressful and disruptive. We need to have a reasonable on-call process that balances the needs of the business with the well-being of our team.</p>
<p>At Bellroy we have found the key elements to our on-call process are:</p>
<ul>
<li><p>We treat our team with kindness
We rotate on-call responsibilities to distribute the workload. We run a two week shift with at least a month between the end of one shift and the start of the next one. Team members get extra time off as compensation for the imposition of taking an on-call shift. If an out of hours response is necessary, extra time off is also allocated. Never expect someone who’s been up half the night dealing with fires to complete a full day of work the next day.</p></li>
<li><p>Our alerts are actually critical enough to disturb someone.
Ensure that a critical severity alert is actually critical. Waking someone up at 3am for a failed background process that can wait for the next day should not happen. Paging someone should be for a problem that is causing (or could cause) significant business disruption. Every calculated alarm should have some fault tolerance in it; a single bad data point should not trigger a page event. Alarms should be clear, informative and actionable by the team member.</p></li>
<li><p>Our documentation is accessible and up to date
Team members should have access to clear and up-to-date documentation so they can quickly identify and resolve issues. We use <a href="https://backstage.io/">backstage</a> and we’ve found that storing the documentation in the git repository with the code helps. Keeping documentation up to date is a struggle as it will drift from reality. We include reviewing and updating documentation into our regular dependency update cycle. We also maintain a knowledge base around incidents and alerts that we’ve seen more than once.</p></li>
<li><p>We have clear expectations and escalation paths
Team members should understand their responsibilities and know how long they have to respond to an incident. In the case that an incident is not acknowledged or can’t be solved by the on-call person, there should be a clear escalation path.</p></li>
<li><p>We train our team
Make sure all team members have access to adequate training so they are able to actually fix problems as they arise.</p></li>
<li><p>We work towards continuous improvement
No process is perfect and there is always room for improvement. After each significant incident we write a post mortem to record the resolution and root cause. Then we make recommendations on how to mitigate similar events in the future.</p></li>
</ul>
<p>On-call is a necessary evil, but we can make it much less of a burden. Like most online businesses we need support 24/7 to minimise the impact of incidents on our systems. By implementing these elements we have developed a fair process that ensures support for our critical systems and a healthy work-life balance for our team.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Mon, 20 Mar 2023 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/keeping-on-call-calm.html</guid>
    <dc:creator>Cat Truscott</dc:creator>
</item>
<item>
    <title>Our technology stack, and how we got here</title>
    <link>https://exploring-better-ways.bellroy.com/our-technology-stack-and-how-we-got-here.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Our technology stack, and how we got here</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/mike_ai_avatar.jpeg"
              alt="Mike Webb profile image"
            />
            <a class="no-underline font-bold" href="/contributors/mike_webb.html">Mike Webb</a>
            <small>2023-03-01</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>We’ve come a long way in terms of our technology stack since launching the first version of
<a href="https://bellroy.com">bellroy.com</a> on <a href="https://www.shopify.com/">Shopify</a>, way back in 2010. In this
blog post, we delve into Bellroy’s technical history, and the company’s journey through
various platforms and languages. We also reveal some of the challenges we faced, and
innovative solutions we employed to overcome them.</p>
<p>Bellroy’s initial foray into e-commerce was through the popular Shopify
platform. Although the platform was easy to set up and use, we soon ran into limitations customising
it to meet Bellroy’s unique needs. As our reputation grew internationally, we needed a
solution with multi-currency and multi-language support without having to maintain separate
configurations for each combination. Thus, the company decided to migrate
to <a href="https://business.adobe.com/au/products/magento/magento-commerce.html">Magento</a>, a platform that -
at the time - seemed a better fit for our needs.</p>
<p>We soon discovered that Magento presented its own set of challenges. Particularly: finding skilled
developers who could work with the platform, and smoothly migrating product configurations between
staging and production environments once we’d tweaked them. These challenges led the company to
explore other options, ultimately resulting in a full website build using
<a href="https://rubyonrails.org/">Ruby on Rails</a> and <a href="https://reactjs.org/">React</a> in 2014. This decision
happened to have a positive effect on hiring; because those were niche technologies at the time,
they attracted a certain type of developer - the type who naturally fitted well with Bellroy’s
culture and values. These were passionate hobbyists who loved to get better at their craft.</p>
<p>The new website initially served Bellroy well, but as the company continued to grow, the codebase
became increasingly difficult to maintain, and - despite having a more capable team - prone to
costly mistakes. It became clear that we needed an even more robust and reliable technology stack.</p>
<p>Bellroy’s first experience with functional programming came in 2018 through the development of a new
version of our checkout using <a href="https://elm-lang.org/">Elm</a>, a statically typed functional
programming language for front-end web development. Errors all but evaporated, and we could refactor
and add new features with confidence. The positive experience with Elm inspired us to explore other
statically typed functional programming languages. We began to experiment with the
<a href="https://sorbet.org">Sorbet</a> type-system and <a href="https://dry-rb.org/gems/dry-monads">dry-monads</a>
library for <a href="https://www.ruby-lang.org/en/">Ruby</a>. We eventually committed to migrating significant
parts of our codebase to <a href="https://www.haskell.org/">Haskell</a> in 2019. With Elm and Haskell we found
again - as we had with Ruby and React - that the majority of developers who gravitated towards those
languages were focused on continuous improvement and the craft of programming, which proved to be a
good fit for Bellroy.</p>
<p>Haskell and Elm are not without their challenges. Haskell has a steep learning curve and - compared
to Ruby - low penetration. A few of our Ruby developers decided to move elsewhere
rather than learn it, and it took months for those who stayed to get to grips with it. Elm is a
solid language but suffers from <a href="https://iselmdead.info/">being perceived as abandoned</a> by its
creator, despite having an active community. However, these languages have positive
characteristics that are unignorable. We regularly have the experience of releasing new tools and
services that <em>just work</em> - first time - and we don’t have to touch for months or years at a time
beyond package updates. The very first service we deployed is responsible for all
of the product images on bellroy.com, and has remained functionally unchanged since 2019.</p>
<p>In Ruby, we would write and test code defensively, trying to cater for every likely possibility. In
Haskell and Elm, we rely on type design, the compiler and property tests to give us rock-solid
guarantees that the code does only what we want it to do. As we’ve deprecated parts of our Ruby
codebase, the Haskell equivalent is a fraction of the size.</p>
<p><img src="/images/2303_haskell_vs_ruby.png" alt="A graph of Haskell vs Ruby lines of code since January 2022" /></p>
<p>We decided to explore statically typed functional programming languages - like Elm and Haskell - for
their reliability, scalability, and maintainability. By reducing the complexity of code and the
potential for human error, programming languages like these can save companies time and resources,
allowing them to focus on the growth and development of their business rather than fighting fires.
This has certainly been our experience, and we hope that this post will encourage others to
consider these languages for their next projects.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Wed, 01 Mar 2023 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/our-technology-stack-and-how-we-got-here.html</guid>
    <dc:creator>Mike Webb</dc:creator>
</item>
<item>
    <title>The unreasonable effectiveness of polymorphic records</title>
    <link>https://exploring-better-ways.bellroy.com/the-unreasonable-effectiveness-of-polymorphic-records.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>The unreasonable effectiveness of polymorphic records</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/clement.jpg"
              alt="Clément Delafargue profile image"
            />
            <a class="no-underline font-bold" href="/contributors/clement_delafargue.html">Clément Delafargue</a>
            <small>2022-11-01</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>Record types are an important part of typed functional programming languages. Separating data
from behaviour creates the need for a convenient representation of key-value pairs. Most of
the time, such records are <em>monomorphic</em>: each field has a specific type. Using
record types this way (forgetting Haskell record idiosyncrasies for a minute) should be familiar
to most of you. Haskell is also well known for its pervasive use of <em>parametric polymorphism</em>;
as always, digging at the intersection of two interesting ideas is time well spent. Let’s have
a look at a few patterns combining records and polymorphism. It should be fun!</p>
<h2 id="step-0-monomorphic-records">Step 0: Monomorphic Records</h2>
<p>Most records are monorphic. Here, the record type <code>Account</code> has kind <code>Type</code>: it is a regular type,
with no type parameters.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Account</span> <span class="ot">=</span> <span class="dt">Account</span> {</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ot">  accountId ::</span> <span class="dt">Int</span>,</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ot">  fullName ::</span> <span class="dt">Text</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> stock (<span class="dt">Eq</span>, <span class="dt">Show</span>, <span class="dt">Generic</span>)</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> anyclass (<span class="dt">FromJSON</span>, <span class="dt">ToJSON</span>)</span></code></pre></div>
<p>The Haskell ecosystem provides us with quite a lot of tooling: here we derive <code>Eq</code> and <code>Show</code> instances,
as well as <code>Generic</code>. <code>Generic</code> acts as an extension point, allowing other libraries to gain access to
structural information and provide more tooling for free (<em>conditions may apply</em>). In this case, <code>Generic</code>
allows us to derive JSON codecs. <code>aeson</code>’s <code>FromJSON</code> and <code>ToJSON</code> provide conversion from/to JSON objects shaped
like our record.</p>
<p>This kind of tooling is not specific to Haskell, and is becoming commonplace even in more mainstream languages
like Java.</p>
<p>Let’s have a look at more sophisticated use cases, and see how far we can go.</p>
<h2 id="step-1-the-humble-wrapper">Step 1: The Humble Wrapper</h2>
<p>Some records are intended as metadata wrappers around other data. Let’s consider <em>pagination</em>: instead of
returning a list of all <code>Account</code> values we want to return only a limited number of them, with pointers
to more accounts. One naive way to do that would be the following:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">PaginatedAccounts</span> <span class="ot">=</span> <span class="dt">PaginatedAccounts</span> {</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ot">  pages ::</span> <span class="dt">Int</span>,</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="ot">  previous ::</span> <span class="dt">Maybe</span> <span class="dt">Int</span>,</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="ot">  next ::</span> <span class="dt">Maybe</span> <span class="dt">Int</span>,</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="ot">  items ::</span> [<span class="dt">Account</span>]</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> stock (<span class="dt">Eq</span>, <span class="dt">Show</span>, <span class="dt">Generic</span>)</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> anyclass (<span class="dt">FromJSON</span>, <span class="dt">ToJSON</span>)</span></code></pre></div>
<p>This is unsatisfying: pagination is a cross-cutting concern that
appears in a lot of places. Creating a new <code>Paginated-</code> variant for each type would require a lot of
boilerplate code and would make room for inconsistencies. So we definitely want to be describe pagination
once, and then use it across our application.</p>
<p>In that case, making the items field polymorphic will do just what
we want. We can still derive typeclasses like <code>Eq</code>, <code>Show</code>, as well
as, for instance, <code>aeson</code> classes like <code>FromJSON</code> and <code>ToJSON</code>. The
compiler will make sure that, when using these instances, <code>a</code> is
implementing them as well.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Paginated</span> a <span class="ot">=</span> <span class="dt">Paginated</span> {</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ot">  pages ::</span> <span class="dt">Int</span>,</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ot">  previous ::</span> <span class="dt">Maybe</span> <span class="dt">Int</span>,</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="ot">  next ::</span> <span class="dt">Maybe</span> <span class="dt">Int</span>,</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="ot">  items ::</span> [a]</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> stock (<span class="dt">Eq</span>, <span class="dt">Show</span>, <span class="dt">Generic</span>)</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> anyclass (<span class="dt">FromJSON</span>, <span class="dt">ToJSON</span>)</span></code></pre></div>
<p>Note that <code>a</code> is the type of <em>one</em> paginated item, not of the list of items.</p>
<h3 id="supercharging-extensions">Supercharging extensions</h3>
<p>Deriving <code>Eq</code>, <code>Show</code> or <code>aeson</code> instances feel natural (after all, many other languages provide similar mechanisms). But this is Haskell, and we shouldn’t be afraid to dream a little bigger.
GHC can derive <code>Functor</code>, <code>Foldable</code>, and <code>Traversable</code> (with the help of <code>DeriveTraversable</code>, which is enabled by default in the <code>GHC2021</code> extension set) for us.
With them, the humble wrapper can be used in many more contexts.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>  …</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>  <span class="kw">deriving</span> stock (…, <span class="dt">Functor</span>, <span class="dt">Foldable</span>, <span class="dt">Traversable</span>)</span></code></pre></div>
<p>The humble wrapper makes no assumptions about its contents. It
lets you derive typeclass instances with no hand-holding, happily
threading dependencies for you. It is now also more versatile, thanks
to the <code>Traversable</code> instance and its friends. It lets you work with
intermediate structures inferred on the go, without the need to plan
them in advance.</p>
<p>For cross-cutting concerns that are highly generic, simple polymorphic records like this are a great fit. The <code>Paginated</code> record
is defined separately from the items it will hold, and typeclasses take care of encoding dependencies. Sadly, not all problems
look like this. In some cases, there is more coupling between the record itself and the moving parts it contains.</p>
<h2 id="step-1-bis">Step 1-bis</h2>
<p>Let’s look at a different example. Here we want to model a database record, before and after insertion in a database.
Before insertion, the primary key does not exist yet. It is generated by the database server when creating the database row.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">DatabaseRecord</span> pk <span class="ot">=</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">DatabaseRecord</span> {</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="ot">    recordId ::</span> pk,</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="ot">    name ::</span> <span class="dt">Text</span>,</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="ot">    dateOfBirth ::</span> <span class="dt">Date</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>  }</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>  <span class="kw">deriving</span> stock (<span class="dt">Eq</span>, <span class="dt">Show</span>, <span class="dt">Generic</span>, <span class="dt">Functor</span>, <span class="dt">Foldable</span>, <span class="dt">Traversable</span>)</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>  <span class="kw">deriving</span> anyclass (<span class="dt">FromJSON</span>, <span class="dt">ToJSON</span>)</span></code></pre></div>
<p>Here, the idea is not to use the full breadth of available types for <code>pk</code>. It is intended to be used for two types: unit (<code>()</code>) and
<code>RecordId</code> (a type modeling a primary key for the given database table). Instead of being a reusable wrapper, <code>DatabaseRecord</code> is only
intended as being reused in two versions <code>DatabaseRecord ()</code>, and <code>DatabaseRecord RecordId</code>: one version is meant to represent a
row prior to insertion, when the primary key is not yet known. The other represents a full record after insertion, where we know the generated primary key.</p>
<p>This is merely a shift of focus from the first example. We’re still using a type parameter to make a field polymorphic. Doing so,
we still get typeclasses instances for free. Note that the <code>aeson</code> instances, while available through generics, may not make sense in
all contexts (ignoring for a minute the fact that directly serializing a database row to JSON is usually a bad idea in actual codebases):
For <code>DatabaseRecord RecordId</code>, the automatic derivation will serialize it as a JSON object, deferring to <code>ToJSON RecordId</code> for the contents of <code>recordId</code>.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;recordId&quot;</span><span class="fu">:</span> <span class="st">&quot;01GJYP9AST5HSBJKBMJBNT28V2&quot;</span><span class="fu">,</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;name&quot;</span><span class="fu">:</span> <span class="st">&quot;Pink Floyd&quot;</span><span class="fu">,</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;dateOfBirth&quot;</span><span class="fu">:</span> <span class="st">&quot;1979-11-30&quot;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>For <code>DatabaseRecord ()</code>, we would expect the <code>recordId</code> field to be either absent or set to <code>null</code>. Sadly, that is not what happens.
Since <code>recordId</code> has type <code>()</code>, its JSON representation will be computed in accordance with <code>ToJSON ()</code>, which is… <code>[]</code>. This
behaviour is surprising, but makes sense: a JSON array can be seen as a tuple, and the unit type can be seen as an empty tuple. So
unit can be seen as an empty JSON array.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;recordId&quot;</span><span class="fu">:</span> <span class="ot">[]</span><span class="fu">,</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;name&quot;</span><span class="fu">:</span> <span class="st">&quot;Pink Floyd&quot;</span><span class="fu">,</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;dateOfBirth&quot;</span><span class="fu">:</span> <span class="st">&quot;1979-11-30&quot;</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>It is always possible to create a type that’s isomorphic to <code>()</code> and to give it a <code>ToJSON</code> instance that serializes to <code>null</code>, but that becomes tedious. Worse, the easiest
thing to implement (just using <code>()</code> and generic-derivated instances) will give undesired behaviour.</p>
<p>The problem here is that we are using a very polymorphic type when we are only expecting to use it in two concrete implementations.
The openness here is both a blessing and a curse: we get things for free, but we allow unwanted states to exist.</p>
<h2 id="step-2">Step 2</h2>
<p>Let’s take a step back and think about what we actually want. We don’t care about the actual type of the <code>recordId</code> field: when it is present, its shape is fixed. We mostly care about the <em>context</em> of whether it is there or not.</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">DatabaseRecord</span> (<span class="ot">f ::</span> <span class="dt">Type</span> <span class="ot">-&gt;</span> <span class="dt">Type</span>) <span class="ot">=</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">DatabaseRecord</span> {</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="ot">    recordId ::</span> f <span class="dt">RecordId</span>,</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="ot">    name ::</span> <span class="dt">Text</span>,</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="ot">    dateOfBirth ::</span> <span class="dt">Date</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a>  }</span></code></pre></div>
<p>Here, we are not taking a regular type parameter, but rather a type <em>constructor</em> parameter (its kind is <code>Type -&gt; Type</code>). This lets us
pin the concrete <code>RecordId</code> type in the record definition. Now, what can <code>f</code> be? We want to cover two cases:</p>
<ul>
<li>there is a <code>recordId</code></li>
<li>there is no <code>recordId</code></li>
</ul>
<p>For the first case, we can use <a href="https://hackage.haskell.org/package/base-4.17.0.0/docs/Data-Functor-Identity.html#t:Identity"><code>Identity</code></a> (<code>newtype Identity a = Identity { runIdentity :: a }</code>). It acts as a wrapper around any <code>a</code>.
More importantly, it fits the <code>Type -&gt; Type</code> kind.
So <code>DatabaseRecord Identity</code> provides us with what we want: a record carrying a primary key.</p>
<p>Now for the second case, we don’t want any value, but we still need the <code>Type -&gt; Type</code> kind. For this, we can use the <code>Proxy</code> type.
Defined as <code>data Proxy a = Proxy</code>, it carries a <em>phantom type</em>. <code>a</code> appears in the left-hand side of the declaration, but is absent
from the right-hand side. This means that in <code>Proxy a</code>, the type <code>a</code> only appears at the type level. The actual <em>value</em> <code>Proxy</code> is
a constructor with no arguments, so there is no value of type <code>a</code> actually carried at run time. This is used a lot with type-level
programming, but here we are just trying to fullfil the <code>Type -&gt; Type</code> kind without providing an actual value.
So <code>DatabaseRecord Proxy</code> provides us with what we want: a record <em>not</em> carrying a primary key.</p>
<p>Another benefit of taking the <em>context</em> as a parameter instead of the actual type is that the <em>context</em> can be reused for different fields. Database rows usually carry a <code>created_at</code> field that is also populated when the row is inserted. With the <code>f</code> parameter, it can
be added to the record definition with minimal fuss:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">DatabaseRecord</span> (<span class="ot">f ::</span> <span class="dt">Type</span> <span class="ot">-&gt;</span> <span class="dt">Type</span>) <span class="ot">=</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">DatabaseRecord</span> {</span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="ot">    recordId ::</span> f <span class="dt">RecordId</span>,</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="ot">    name ::</span> <span class="dt">Text</span>,</span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="ot">    dateOfBirth ::</span> <span class="dt">Date</span>,</span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="ot">    createdAt ::</span> f <span class="dt">UTCTime</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a>  }</span></code></pre></div>
<p>Had we chosen the previous way to model <code>DatabaseRecord</code>, we would now have</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">DatabaseRecord</span> pk cat <span class="ot">=</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">DatabaseRecord</span> {</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="ot">    recordId ::</span> pk,</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="ot">    name ::</span> <span class="dt">Text</span>,</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="ot">    dateOfBirth ::</span> <span class="dt">Date</span>,</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="ot">    createdAt ::</span> cat</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a>  }</span></code></pre></div>
<p>Using a type constructor as a parameter is a very convenient way to model the presence or absence of certain fields while still
pinning the expected types of those fields, when they should be there. For one field, this is good because it makes the actual
types clear, but it becomes really interesting when there are multiple fields with different types that share
the same context.</p>
<p>The flip side, as always when moving with more constrained solutions, is that we reduce polymorphism and then cannot benefit from
what it provides. Here it’s not possible to automatically derive typeclasses: we have to hold GHC’s hand:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE StandaloneDeriving #-}</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE UndecidableInstances #-}</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a>…</span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> <span class="kw">instance</span> (<span class="dt">Show</span> (f <span class="dt">RecordId</span>), <span class="dt">Show</span> (f <span class="dt">UTCTime</span>))  <span class="ot">=&gt;</span> <span class="dt">Show</span> (<span class="dt">D</span> f)</span></code></pre></div>
<p>Also, we have pinned the concrete type of the primary key and the creation date. However, the <code>f</code> type constructor is still unconstrained,
so we can still model <code>DatabaseRecord []</code> or <code>DatabaseRecord (ContT Void (Coyoneda (Cotambara (Tannen Identity (Kleisly IO))) Int))</code>
(for instance).</p>
<h2 id="step-3-trees-that-grow-or-just-branches-maybe">Step 3: Trees That Grow (or just branches, maybe?)</h2>
<p>This is where we bring out the big guns. The <a href="https://www.microsoft.com/en-us/research/uploads/prod/2016/11/trees-that-grow.pdf">Trees That Grow</a> paper describes a mechanism that lets us do exactly what
we want: explicitly choosing types based on the context, at the <em>small</em> cost of using a <em>few</em> advanced haskell features.</p>
<p>The idea here is to start by listing the contexts we care about:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ot">{-# LANGUAGE DataKinds #-}</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">DbContext</span> <span class="ot">=</span> <span class="dt">BeforeInsertion</span> <span class="op">|</span> <span class="dt">AfterInsertion</span></span></code></pre></div>
<p>With the help of <code>DataKinds</code>, this humble enum is promoted to the realm of types: <code>DbContext</code> can be used as a <em>kind</em>, in addition to a type,
and the constructors <code>'BeforeInsertion</code> and <code>'AfterInsertion</code> (note the leading quotes: while not strictly required, they let us make it explicit when using types promoted from data constructors) are created as types in addition to <code>BeforeInsertion</code> and <code>AfterInsertion</code> being terms.</p>
<p>So this lets us have an enum at the <em>type level</em>, describing the context we are in. So (slight spoiler) we will define <code>DatabaseRecord</code> as <code>data DatabaseRecord (context :: DbContext) = …</code>. This way, We can only talk about <code>DatabaseRecord BeforeInsertion</code> and <code>DatabaseRecord AfterInsertion</code>.</p>
<p>Next is to decide the actual types of the <code>recordId</code> and <code>createdAt</code> fields. We cannot do this inside the definition of <code>DatabaseRecord</code>. What we need to do is to define something like a function: <code>DbContext -&gt; Type</code>, that operates at the type level. With the help of another extension (<code>TypeFamilies</code>), we can do just that:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="kw">family</span> <span class="dt">RecordIdType</span> (<span class="ot">context ::</span> <span class="dt">DbContext</span>) <span class="kw">where</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">RecordIdType</span> <span class="dt">BeforeInsertion</span> <span class="ot">=</span> ()</span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a>  <span class="dt">RecordIdType</span> <span class="dt">AfterInsertion</span> <span class="ot">=</span> <span class="dt">RecordId</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> <span class="kw">family</span> <span class="dt">CreatedAtType</span> (<span class="ot">context ::</span> <span class="dt">DbContext</span>) <span class="kw">where</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a>  <span class="dt">CreatedAtType</span> <span class="dt">BeforeInsertion</span> <span class="ot">=</span> ()</span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a>  <span class="dt">CreatedAtType</span> <span class="dt">AfterInsertion</span> <span class="ot">=</span> <span class="dt">UTCTime</span></span></code></pre></div>
<p>And finally, we can define our brand new <code>DatabaseRecord</code>:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">DatabaseRecord</span> (<span class="ot">context ::</span> <span class="dt">DbContext</span>) <span class="ot">=</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">DatabaseRecord</span> {</span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="ot">    recordId ::</span> <span class="dt">RecordIdType</span> context,</span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a><span class="ot">    name ::</span> <span class="dt">Text</span>,</span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a><span class="ot">    dateOfBirth ::</span> <span class="dt">Date</span>,</span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a><span class="ot">    createdAt ::</span> <span class="dt">CreatedAtType</span> context</span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a>  }</span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a><span class="kw">deriving</span> <span class="kw">instance</span> (<span class="dt">Show</span> (<span class="dt">RecordIdType</span> context), <span class="dt">Show</span> (<span class="dt">CreatedAtType</span> context)) <span class="ot">=&gt;</span> <span class="dt">Show</span> (<span class="dt">DatabaseRecord</span> context)</span></code></pre></div>
<p>All done! This is obviously the perfect solution, so you should use this technique everytime. I promise nothing bad will happen.</p>
<p>More seriously, this encoding is the tightest implementation of the problem statement. As you can see it is also a fairly complex one,
and like before it requires manually declaring <code>deriving instance</code> dependencies.</p>
<h2 id="what-did-we-learn">What did we learn?</h2>
<p>In a not-so-surprising turn of events, it appears that advanced
type-level features give us more expressive power while making things
more complex and applicable in fewer contexts.</p>
<p>“Simple” polymorphic records crop up a lot in day-to-day web
programming and are very effective at keeping APIs consistent. This
pattern interacts nicely with automatic deriving, especially for JSON
instances. For better or worse, it also doesn’t prevent us developers
from modeling nonsensical types.</p>
<p>Taking type constructors (of kind <code>Type -&gt; Type</code>) as a type parameter is
surprisingly effective at modeling context-dependent data requirements.
Higher kinded types have a scary reputation outside Haskell, but they can
have concrete use-cases.</p>
<p>Finally, sophisticated techniques such as trees that grow are definitely not
something one would use every day, but they can be extremely effective at
reducing redundancy in complex data modeling. Seeing such advanced
mechanisms all mesh together to give rise to a concrete use-case is part of
what makes Haskell, Haskell.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Tue, 01 Nov 2022 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/the-unreasonable-effectiveness-of-polymorphic-records.html</guid>
    <dc:creator>Clément Delafargue</dc:creator>
</item>
<item>
    <title>Assembling reliable records from semi-reliable systems</title>
    <link>https://exploring-better-ways.bellroy.com/assembling-reliable-records-from-semi-reliable-systems.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Assembling reliable records from semi-reliable systems</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="https://en.gravatar.com/avatar/fdc0b9bf1c68e3f6824e05631913edc5/"
              alt="Chad Musick profile image"
            />
            <a class="no-underline font-bold" href="/contributors/chad_musick.html">Chad Musick</a>
            <small>2022-10-31</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/data.html">Data and Analytics</a>
          </div>
        </header>
        <section><p>Ensuring good data quality is fundamental to effective <a href="https://www.cio.com/article/202183/what-is-data-governance-a-best-practices-framework-for-managing-data-assets.html">data governance</a>, and this blog post describes one method of managing the effects of out-of-order arrival and latency, which any networked system can encounter.</p>
<p>Record-keeping in commerce is an old task. Some of the earliest written records were related to commerce, <a href="https://britishmuseum.withgoogle.com/object/the-first-writing-counting-beer-for-the-workers">such as this stone payslip</a>. These days, most companies will be storing these records on paper, in spreadsheets, or in databases. A few companies will have a cohesive <a href="https://www.ibm.com/topics/data-fabric">data fabric</a> that helps to both manage records and make effective use of them.</p>
<p>One of the technically trickier aspects of ensuring good data quality is that information may arrive from a set of outside sources, and those sources may supply information at different paces and different places wearing different faces. There are many ways to solve this problem. Here, we present one such method that provides good visibility into the current status of records and is robust against the order of arrival of information.</p>
<p>Let’s assume we have a very simple record with the following structure.</p>
<table>
<colgroup>
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
<col style="width: 20%" />
</colgroup>
<thead>
<tr>
<th>Order ID</th>
<th>Product</th>
<th>Package tracking</th>
<th>Delivered on</th>
<th>Customer rating</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="https://bellroy.com/products/sling-mini-mirum-edition?color=mirum_black&amp;material=mirum#slide-0">Sling Mini MIRUM® Edition</a></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p>If everything goes exactly right, we will receive the package tracking information, then the delivery information, and then (we hope) the customer rating. Across millions of transactions, though, this process will probably break at least a few times.</p>
<p>Here’s one way it could break.</p>
<pre class="mermaid"><code>sequenceDiagram
Bellroy-&gt;&gt;Warehouse: Let&#39;s ship Order 1
Warehouse-&gt;&gt;Shipper: Here&#39;s Order 1 to ship
Shipper-&gt;&gt;Warehouse: Here&#39;s your tracking id
Note right of Bellroy: Network error
Customer-&gt;&gt;Bellroy: I love it! 5 stars
Bellroy-&gt;&gt;Customer: We&#39;re glad you love it. Thanks for letting us know.
Bellroy--&gt;&gt;Warehouse: We&#39;re missing tracking&lt;br /&gt;for Order 1
Warehouse-&gt;&gt;Bellroy: Here it is
Bellroy-&gt;&gt;Shipper: What&#39;s the delivery status?
Shipper-&gt;&gt;Bellroy: We delivered last week</code></pre>
<p>To construct this record requires information from multiple systems. That information may not arrive at all (e.g., due to a network error) or may arrive out of order (e.g., receiving feedback before the item is known to have been shipped). Any system that relies on 100% message arrival and 100% in-order arrival is likely to produce poor quality data. Fortunately, we can fix this by a simple process.</p>
<ol>
<li>Document the ‘good’ states of a record.</li>
<li>Document the acceptable latency for information arrival.</li>
<li>Keep track of the timing of information arrival.</li>
<li>Check that the state of a record is in the ‘good’ state for a process before sending the record to that process.</li>
<li>Re-request late information or otherwise mitigate the problem, and possibly alert someone. This can be done via email or most workplace collaboration tools.</li>
</ol>
<p>A typical toolchain for this will include a database, an ELT/ETL tool, an orchestration tool, and a messaging system. At present we use <a href="https://www.postgresql.org/">PostgreSQL</a> as our transactional database, <a href="https://nifi.apache.org/">Apache NiFi</a> for ELT and orchestration, <a href="https://slack.com/">Slack</a> for many notifications and <a href="https://www.zabbix.com/">Zabbix</a> for performance monitoring. These communicate via webhooks, database protocols, and <a href="https://kafka.apache.org/">Apache Kafka</a>. Except for Slack, all of these are open-source products.</p>
<p>We’ll write in future posts about our affinity for open-source software and the contributions we make to maintaining and expanding the availability of useful open-source products.</p></section>
        
        <script>
          document.querySelectorAll("pre.mermaid").forEach(el => {
            el.outerHTML = `<div class="mermaid">${el.textContent}</div>`;
          });
        </script>
        <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Mon, 31 Oct 2022 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/assembling-reliable-records-from-semi-reliable-systems.html</guid>
    <dc:creator>Chad Musick</dc:creator>
</item>
<item>
    <title>Welcome!</title>
    <link>https://exploring-better-ways.bellroy.com/welcome.html</link>
    <description><![CDATA[<div class="pt-24 w-full">
  <div
    class="my-16 font-sans prose prose-lg max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl prose-a:text-orange-600 prose-headings:font-serif prose-headings:font-normal prose-ul:list-disc prose-ol:list-decimal prose-li:list-[inherit] prose-img:max-w-full mx-auto px-6 md:px-12 lg:px-16"
  >
    <main>
      <article>
        <header class="text-center">
          <h1>Welcome!</h1>
          <div>
            <img
              class="w-32 mx-auto rounded-full border-8 border-white"
              src="/images/contributors/mike_ai_avatar.jpeg"
              alt="Mike Webb profile image"
            />
            <a class="no-underline font-bold" href="/contributors/mike_webb.html">Mike Webb</a>
            <small>2022-10-19</small>
          </div>
          <div>
            <a class="no-underline font-bold" href="/teams/technology.html">Technology</a>
          </div>
        </header>
        <section><p>Welcome. To the space where Bellroy’s crew gets technical. Here, we aim to share some of the ideas,
tools and models that drive Bellroy behind the scenes.</p>
<p>When the team at Bellroy first considered starting this blog, we asked ourselves “what should this
blog do that <a href="https://bellroy.com/journal">The Journal</a> doesn’t?” Bellroy’s mission is to “<a href="https://bellroy.com/about-us">…help you
move through the world with ease and energy. While helping the world, and our crew,
flourish</a>”. Part of helping the world and our crew flourish is sharing
the challenges and innovations we encounter along the way. And this blog allows us to provide deeper
insight into those. We’re committed to helping our people learn, and one of the best ways to learn
is by teaching. By having our crew share some of their eureka moments, we hope that it can solidify
things for them, as well as help someone else out there solve a problem.</p>
<p>We’re proud of our products and the way we present them to the world. A hell of a lot of
deliberate thought and effort goes into this. While our website presents the smooth veneer of the
‘duck above water’, below the water the legs (that is, our team) are working double-time and in all
directions to keep things that way. We’re pleased to provide this little window into how Bellroy
operates behind the scenes.</p>
<p>We’ve built this blog so that any team at Bellroy can contribute posts, and to start we have a
great lineup of technical posts in our schedule. Over the next few months, we’ll publish posts on
topics such as:</p>
<ul>
<li>Our software engineering philosophy</li>
<li>Our systems architecture</li>
<li>Our infrastructure</li>
<li>Our machine-led and human-led processes</li>
<li>Our approaches to problem solving</li>
<li>… and the business drivers behind many of these</li>
</ul>
<p>We hope you enjoy learning more about how we do what we do - as we continue to explore better ways.</p>
<hr />
<p>One last technical note - we love functional programming and open-source software. We depend on
open-source products and contribute back wherever we can. For this blog, we use
<a href="https://github.com/jaspervdj/hakyll">Hakyll</a> as our static site generator. We use
<a href="https://github.com/NixOS/nix">Nix</a> to manage our development and build environments. The blog
repository was seeded using <a href="https://github.com/rpearce/hakyll-nix-template">Robert Pearce’s excellent Hakyll + Nix template</a>.</p></section>
        
      </article>
    </main>
  </div>
</div>
]]></description>
    <pubDate>Wed, 19 Oct 2022 00:00:00 UT</pubDate>
    <guid>https://exploring-better-ways.bellroy.com/welcome.html</guid>
    <dc:creator>Mike Webb</dc:creator>
</item>

    </channel>
</rss>
