<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://www.yellowduck.be/pretty-atom-feed-v3.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link href="https://www.yellowduck.be" rel="alternate"/>
  <link href="https://www.yellowduck.be/posts/feed" rel="self"/>
  <author>
    <name>Pieter Claerhout</name>
    <email>pieter@yellowduck.be</email>
  </author>
  <id>https://www.yellowduck.be/posts/feed</id>
  <title>🐥 YellowDuck.be</title>
  <updated>2026-05-13T17:00:00Z</updated>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/anti-corruption-layer-in-elixir-phoenix-keep-your-domain-clean" rel="alternate"/>
    <content type="html">&lt;p&gt;When you integrate an external service — a payment provider, a shipping API, a CRM — you face a quiet but persistent risk: the external system&apos;s model starts leaking into your own. Status strings, provider-specific IDs, and SDK structs end up scattered across your business logic. Changing providers later means touching code you should never have had to change.&lt;/p&gt;
&lt;p&gt;The Anti-Corruption Layer (ACL) is a pattern from Domain-Driven Design that puts an explicit translation boundary between your domain and the external world. Everything provider-specific lives in one place. Your domain never knows it exists.&lt;/p&gt;
&lt;p&gt;This post walks through implementing it in Elixir and Phoenix, using a payment integration as the running example.&lt;/p&gt;
&lt;h1&gt;The Problem&lt;/h1&gt;
&lt;p&gt;Suppose you&apos;re integrating Stripe directly. Without a boundary, it&apos;s easy to write something like this:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;MyApp.Orders&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;complete_order&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Req&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;https://api.stripe.com/v1/payment_intents&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;           &lt;span class=&quot;string-special-symbol&quot;&gt;auth: &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:basic&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;stripe_key&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;           &lt;span class=&quot;string-special-symbol&quot;&gt;form: &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;amount: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;total_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;currency: &lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;eur&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;confirm: &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;         &lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;body: &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;status&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;succeeded&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span class=&quot;module&quot;&gt;Orders&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;mark_paid&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;body: &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;error&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;message&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works on day one. But now your &lt;code&gt;Orders&lt;/code&gt; context knows that payments have a &lt;code&gt;&quot;succeeded&quot;&lt;/code&gt; status string, that IDs come from &lt;code&gt;intent[&quot;id&quot;]&lt;/code&gt;, and that Stripe&apos;s API lives at that specific URL. If you ever swap providers — or just want to test without hitting Stripe — you have to unpick all of this from business logic.&lt;/p&gt;
&lt;h1&gt;Core Concepts in Elixir&lt;/h1&gt;
&lt;p&gt;The ACL pattern maps cleanly to Elixir idioms:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DDD concept&lt;/th&gt;
&lt;th&gt;Elixir equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Domain model&lt;/td&gt;
&lt;td&gt;Plain &lt;code&gt;defstruct&lt;/code&gt; (not an Ecto schema)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port / interface&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;@behaviour&lt;/code&gt; module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACL implementation&lt;/td&gt;
&lt;td&gt;A module implementing the behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain service&lt;/td&gt;
&lt;td&gt;A context module that only calls the behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wiring&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Application.get_env/2&lt;/code&gt; and config files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Step 1: Define Your Domain Structs&lt;/h1&gt;
&lt;p&gt;These live in your domain and use your language — not Stripe&apos;s.&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.PaymentResult&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;type &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:pending&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:captured&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:declined&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:refunded&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;enforce_keys &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:status&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:currency&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defstruct&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:status&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:currency&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span class=&quot;keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.PaymentEvent&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;type &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:payment_captured&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:payment_declined&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:payment_refunded&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;enforce_keys &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:type&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:payment_id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defstruct&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:type&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:payment_id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No Stripe types, no raw maps, no magic strings. If you switch providers, these structs stay exactly as they are.&lt;/p&gt;
&lt;h1&gt;Step 2: Define the Port Behaviour&lt;/h1&gt;
&lt;p&gt;The behaviour declares what the domain needs — a contract, not an implementation.&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.PaymentGateway&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;type &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;charge_result&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.PaymentResult&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;type &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;refund_result&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.PaymentResult&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;callback &lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;charge&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;amount_cents&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;currency&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;              &lt;span class=&quot;function-call&quot;&gt;charge_result&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;callback &lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;payment_id&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;refund_result&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The domain context will call through this behaviour. It never imports Stripe modules or touches raw HTTP responses.&lt;/p&gt;
&lt;h1&gt;Step 3: Implement the ACL&lt;/h1&gt;
&lt;p&gt;This is the only module allowed to know anything about Stripe. All HTTP calls and all translation logic live here.&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.Gateways.StripeGateway&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;behaviour &lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Billing.PaymentGateway&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;base_url &lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;https://api.stripe.com/v1&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;impl &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;charge&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span class=&quot;module&quot;&gt;Req&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;post!&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/payment_intents&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span class=&quot;string-special-symbol&quot;&gt;auth: &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:basic&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;secret_key&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span class=&quot;string-special-symbol&quot;&gt;form: &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;          &lt;span class=&quot;string-special-symbol&quot;&gt;amount: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;          &lt;span class=&quot;string-special-symbol&quot;&gt;currency: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;          &lt;span class=&quot;string-special-symbol&quot;&gt;payment_method: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;          &lt;span class=&quot;string-special-symbol&quot;&gt;confirm: &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;          &lt;span class=&quot;string-special-symbol&quot;&gt;&quot;automatic_payment_methods[enabled]&quot;: &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;          &lt;span class=&quot;string-special-symbol&quot;&gt;&quot;automatic_payment_methods[allow_redirects]&quot;: &lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;never&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;        &lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;      &lt;span class=&quot;number&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;translate_intent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      &lt;span class=&quot;comment&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;impl &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;payment_id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;      &lt;span class=&quot;module&quot;&gt;Req&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;post!&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/refunds&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;        &lt;span class=&quot;string-special-symbol&quot;&gt;auth: &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:basic&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;secret_key&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;        &lt;span class=&quot;string-special-symbol&quot;&gt;form: &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;payment_intent: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;payment_id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;      &lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;      &lt;span class=&quot;number&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;translate_refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;      &lt;span class=&quot;comment&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;# --- Translation layer ---&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_intent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;    &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Billing.PaymentResult&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;translate_status&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;amount_cents: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;amount&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;currency: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;intent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;currency&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;50&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;51&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;52&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;53&quot;&gt;    &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Billing.PaymentResult&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;54&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;payment_intent&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;55&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:refunded&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;56&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;amount_cents: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;amount&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;57&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;currency: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;currency&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;58&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;59&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;60&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;61&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_status&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;succeeded&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;do: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:captured&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;62&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_status&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;processing&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;do: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:pending&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;63&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_status&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;requires_payment_method&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;do: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:declined&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;64&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_status&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;canceled&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;do: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:declined&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;65&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_status&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;comment&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;do: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:pending&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;66&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;67&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;secret_key&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;do: &lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;fetch_env!&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:my_app&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:stripe_secret_key&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;68&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pattern matching on &lt;code&gt;translate_status/1&lt;/code&gt; makes the translation explicit and exhaustive. Adding a new Stripe status is a one-line change in exactly one place.&lt;/p&gt;
&lt;h1&gt;Step 4: Write a Clean Domain Context&lt;/h1&gt;
&lt;p&gt;The &lt;code&gt;Billing&lt;/code&gt; context only speaks the behaviour. It has no idea which gateway is wired up.&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;process_payment&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span class=&quot;function-call&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;charge&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;issue_refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;payment_id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span class=&quot;function-call&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;payment_id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;gateway&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span class=&quot;module&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;get_env&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:my_app&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:payment_gateway&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.Gateways.StripeGateway&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing in here ties you to Stripe. The context is pure business logic.&lt;/p&gt;
&lt;h1&gt;Step 5: Wire It Up in Config&lt;/h1&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# config/config.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:my_app&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:payment_gateway&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.Gateways.StripeGateway&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:my_app&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:stripe_secret_key&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;get_env&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;STRIPE_SECRET_KEY&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# config/test.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:my_app&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:payment_gateway&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.Gateways.FakeGateway&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Switching providers in production means changing one config line and shipping a new gateway module. Nothing else changes.&lt;/p&gt;
&lt;h1&gt;Handling Webhooks&lt;/h1&gt;
&lt;p&gt;Webhooks are where external models are most tempting to let through. A Phoenix controller should translate immediately and dispatch a domain event — it should not pass raw Stripe payloads any further.&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;MyAppWeb.StripeWebhookController&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;MyAppWeb&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:controller&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;translate_event&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span class=&quot;module&quot;&gt;Billing&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;handle_payment_event&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span class=&quot;function-call&quot;&gt;send_resp&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:unhandled&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span class=&quot;function-call&quot;&gt;send_resp&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;reason&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;        &lt;span class=&quot;function-call&quot;&gt;send_resp&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;reason&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_event&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;         &lt;span class=&quot;string&quot;&gt;&quot;type&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;payment_intent.succeeded&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;         &lt;span class=&quot;string&quot;&gt;&quot;data&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;object&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;amount&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;       &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Billing.PaymentEvent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;type: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:payment_captured&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;payment_id: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;amount_cents: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_event&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;         &lt;span class=&quot;string&quot;&gt;&quot;type&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;payment_intent.payment_failed&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;         &lt;span class=&quot;string&quot;&gt;&quot;data&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;object&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;amount&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;       &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Billing.PaymentEvent&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;type: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:payment_declined&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;payment_id: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;amount_cents: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;amount&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;defp&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;translate_event&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;comment&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;do: &lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string-special-symbol&quot;&gt;:unhandled&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The controller is the ACL for inbound Stripe events. Once translated, &lt;code&gt;Billing.handle_payment_event/1&lt;/code&gt; receives only &lt;code&gt;%Billing.PaymentEvent&amp;lbrace;&amp;rbrace;&lt;/code&gt; structs — never raw Stripe data.&lt;/p&gt;
&lt;h1&gt;Testing Without a Real Gateway&lt;/h1&gt;
&lt;p&gt;Because the gateway is a behaviour, you can swap in a fake implementation for tests without mocks or HTTP stubs.&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-function&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;Billing.Gateways.FakeGateway&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;behaviour &lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Billing.PaymentGateway&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;impl &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;charge&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;comment&quot;&gt;_amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;comment&quot;&gt;_currency&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;fail_card&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;Card declined&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;charge&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;currency&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;comment&quot;&gt;_source&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Billing.PaymentResult&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;fake_&lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;unique_integer&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:positive&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;string-special&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:captured&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;amount_cents: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;amount_cents&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;currency: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;currency&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span class=&quot;constant&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;impl &lt;/span&gt;&lt;span class=&quot;boolean&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span class=&quot;keyword-function&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;refund&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;payment_id&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Billing.PaymentResult&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;payment_id&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:refunded&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;amount_cents: &lt;/span&gt;&lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;      &lt;span class=&quot;string-special-symbol&quot;&gt;currency: &lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;eur&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your tests call &lt;code&gt;Billing.process_payment/3&lt;/code&gt; exactly as production code does — there is no test-only branch in the domain logic, and no HTTP traffic.&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;successful payment returns a captured result&quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;function-call&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:ok&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Billing.PaymentResult&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:captured&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;           &lt;span class=&quot;module&quot;&gt;Billing&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;process_payment&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;1999&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;eur&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;tok_visa&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;declined card returns an error&quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span class=&quot;function-call&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;string-special-symbol&quot;&gt;:error&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;Card declined&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;           &lt;span class=&quot;module&quot;&gt;Billing&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;process_payment&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;1999&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;eur&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;fail_card&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Elixir-Specific Considerations&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Behaviours vs. protocols.&lt;/strong&gt; Use a &lt;code&gt;@behaviour&lt;/code&gt; when you have multiple implementations of the same concept (payment gateways, SMS providers). Use a &lt;code&gt;protocol&lt;/code&gt; when you want polymorphism across data types you don&apos;t control. For the ACL pattern, behaviours are the right tool.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pattern matching is your translation engine.&lt;/strong&gt; The &lt;code&gt;translate_status/1&lt;/code&gt; clauses above are cleaner than a &lt;code&gt;case&lt;/code&gt; block or a map lookup. If Stripe adds a new status you haven&apos;t handled, the &lt;code&gt;_&lt;/code&gt; clause captures it safely, and you can add a specific clause when you care about it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep Ecto schemas out of the ACL.&lt;/strong&gt; It&apos;s tempting to map directly from a Stripe response onto an Ecto schema. Resist this. Plain structs are easier to reason about, test, and evolve independently from your database shape. Persist only after your domain logic has run.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stateful gateways.&lt;/strong&gt; If your gateway implementation needs its own process — say, to maintain an authenticated session or a connection pool — start it in your application&apos;s supervision tree. The behaviour contract stays the same; the implementation just has a &lt;code&gt;start_link/1&lt;/code&gt; that gets wired into the supervisor.&lt;/p&gt;
&lt;h1&gt;Common Pitfalls&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Returning raw maps from the gateway.&lt;/strong&gt; If &lt;code&gt;charge/3&lt;/code&gt; returns &lt;code&gt;%&amp;lbrace;&quot;id&quot; =&gt; ..., &quot;status&quot; =&gt; &quot;succeeded&quot;&amp;rbrace;&lt;/code&gt;, you&apos;ve moved the contamination rather than removed it. Always return your own structs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Putting translation in the Phoenix controller for everything.&lt;/strong&gt; The controller is the right place for webhook translation (it&apos;s the entry point). But if you&apos;re doing translation inside action handlers for outgoing calls, extract it into the gateway module where it belongs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One ACL module for all external systems.&lt;/strong&gt; If you add a shipping provider, create &lt;code&gt;Shipping.Gateways.DHLGateway&lt;/code&gt; with its own behaviour. Don&apos;t grow &lt;code&gt;StripeGateway&lt;/code&gt; into a general-purpose external-systems module.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Letting provider IDs become first-class domain concepts.&lt;/strong&gt; Storing a Stripe payment intent ID as &lt;code&gt;billing_stripe_id&lt;/code&gt; in your domain is a smell — the domain now knows about Stripe. Store it as &lt;code&gt;external_payment_id&lt;/code&gt; or in a separate gateway-specific record that your ACL manages.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Elixir&apos;s behaviours give you a clean, compiler-checked way to define the boundary between your domain and the outside world. Pattern matching makes the translation logic readable and exhaustive. Swapping configs gives you test isolation without mocks.&lt;/p&gt;
&lt;p&gt;The ACL pattern keeps your core business logic stable regardless of what external APIs do. When Stripe changes a status code or you decide to try a different provider, you update one module. Your domain, your tests, and everything that depends on them stays untouched.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/pattern&quot;&gt;#pattern&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-13T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/anti-corruption-layer-in-elixir-phoenix-keep-your-domain-clean</id>
    <title>🐥 Anti-Corruption Layer in Elixir/Phoenix - Keep your domain clean</title>
    <updated>2026-05-13T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/no-management-needed-anti-patterns-in-early-stage-engineering-teams" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Early-stage founders waste time on management that doesn&apos;t yet matter. Instead of motivating engineers through 996-style cultures or hiring managers, founders should focus on hiring inherently motivated people and maintaining a lightweight, transparent environment. The key is avoiding premature organizational structure—keeping all engineers under one technical leader until reaching 20-50 people, while resisting the temptation to copy Google&apos;s management innovations. Culture should develop through shipping speed, not clever feedback systems.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.ablg.io/blog/no-management-needed&quot;&gt;Continue reading on &lt;strong&gt;www.ablg.io&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-13T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/no-management-needed-anti-patterns-in-early-stage-engineering-teams</id>
    <title>🔗 No management needed: anti-patterns in early-stage engineering teams</title>
    <updated>2026-05-13T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/sqlite-features-you-didnt-know-it-had-json-text-search-cte-strict-generated-columns-wal" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Modern SQLite packs powerful features that most developers overlook. The database supports JSON querying, full-text search via FTS5, window functions and CTEs for analytics, strict typing enforcement, generated columns for derived data, and write-ahead logging for improved concurrency. These capabilities eliminate the need for separate specialized tools, letting teams keep data in a single file while maintaining sophisticated functionality traditionally associated with heavier database systems.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://slicker.me/sqlite/features.htm&quot;&gt;Continue reading on &lt;strong&gt;slicker.me&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sql&quot;&gt;#sql&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sqlite&quot;&gt;#sqlite&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-13T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/sqlite-features-you-didnt-know-it-had-json-text-search-cte-strict-generated-columns-wal</id>
    <title>🔗 SQLite features you didn’t know it had: JSON, text search, CTE, STRICT, generated columns, WAL</title>
    <updated>2026-05-13T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/thinking-elixir-podcast-303-the-taming-of-the-slop" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;EEF 2026 election candidates are out, Elixir-Vibe launches tools to fight AI code slop, erlang_python 3.0.0 embeds CPython into the BEAM, ElixirConf EU 2026 videos are dropping, and more!&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://podcast.thinkingelixir.com/303&quot;&gt;Continue reading on &lt;strong&gt;podcast.thinkingelixir.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-12T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/thinking-elixir-podcast-303-the-taming-of-the-slop</id>
    <title>🔗 Thinking Elixir Podcast 303: The Taming of the Slop</title>
    <updated>2026-05-12T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/intelligent-curation-tagging-for-creative-workflows" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;I struggled with organizing 14,000 photos and wanted an AI to help identify the ones matching my aesthetic. Initially, I relied on a vision model to classify them based on a prose description, but the results were inconsistent and resource-intensive, spiking RAM usage to 15GB. The new architecture focuses on learning from my feedback instead of static prompts. Using CLIP embeddings and a preference model, it tracks my ratings to improve selection. The ingestion process involves measuring technical image qualities and extracting metadata before sending photos to the AI worker. This staged pipeline lets each component fail and retry independently, providing a smooth curation experience. After running just a few sessions, I managed to surface 214 meaningful images out of 3,600 processed. The entire backlog could be managed within a week at this rate!&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://qwelian.com/posts/FINE_SHYT_Intelligent_Curation_Tagging_for_Creative_Workflows&quot;&gt;Continue reading on &lt;strong&gt;qwelian.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-12T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/intelligent-curation-tagging-for-creative-workflows</id>
    <title>🔗 Intelligent curation tagging for creative workflows</title>
    <updated>2026-05-12T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/common-cors-errors-and-how-to-fix-them" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Cross-Origin Resource Sharing (CORS) provides browser security by managing requests across different origins. CORS errors arise when the server does not send back the expected headers, leading to blocked requests in the browser&apos;s console.&lt;/p&gt;
&lt;p&gt;This article outlines common CORS errors, including missing headers and preflight issues, and offers detailed solutions. Developers are advised to configure their server settings correctly to allow cross-origin interactions while maintaining secure API access.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://workos.com/blog/common-cors-errors-and-how-to-fix-them&quot;&gt;Continue reading on &lt;strong&gt;workos.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/frontend&quot;&gt;#frontend&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/http&quot;&gt;#http&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-12T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/common-cors-errors-and-how-to-fix-them</id>
    <title>🔗 Common CORS errors and how to fix them</title>
    <updated>2026-05-12T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/restarting-supervisord-daemons-by-working-directory-on-linux" rel="alternate"/>
    <content type="html">&lt;p&gt;When you run multiple instances of the same Laravel Horizon worker (or any supervisord-managed process) across different deployment directories, you&apos;ll quickly notice that supervisord assigns generic auto-generated names like &lt;code&gt;daemon-282661:daemon-282661_00&lt;/code&gt;. There&apos;s no obvious way to tell which daemon belongs to which application directory — until you know the trick.&lt;/p&gt;
&lt;h1&gt;Finding the working directory of a process&lt;/h1&gt;
&lt;p&gt;On Linux, every running process exposes its current working directory as a symlink under &lt;code&gt;/proc&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;readlink&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/proc/&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;&lt;&lt;/span&gt;&lt;span class=&quot;string-special-path&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;string-special-path&quot;&gt;/cwd&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To check all your Horizon workers at once, grab their PIDs from &lt;code&gt;supervisorctl status&lt;/code&gt; and loop over them:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-repeat&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;keyword-conditional&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;91286&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;91287&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;91288&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;91290&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;keyword-repeat&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;readlink&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/proc/&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;variable-parameter&quot;&gt;/cwd&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-repeat&quot;&gt;done&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Restarting daemons for a specific directory&lt;/h1&gt;
&lt;p&gt;Once you can map PIDs to directories, it&apos;s straightforward to build a script that restarts only the daemons running from a given path:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-directive&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;constant&quot;&gt;TARGET_DIR&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:?&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;Usage:&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;string&quot;&gt; &lt;working-directory&gt;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;constant&quot;&gt;TARGET_DIR&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;realpath&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;TARGET_DIR&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;supervisorctl&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;keyword-repeat&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;function-builtin&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;keyword-repeat&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;RUNNING&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;function-builtin&quot;&gt;continue&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;awk&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;&amp;lbrace;print $1&amp;rbrace;&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-oP&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;pid \K[0-9]+&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span class=&quot;function-builtin&quot;&gt;continue&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;readlink&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;/proc/&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;/cwd&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;string-special-path&quot;&gt;/dev/null&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span class=&quot;keyword-conditional&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;TARGET_DIR&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;keyword-conditional&quot;&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;        &lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;Restarting &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;string&quot;&gt; (pid &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;, dir &lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;)&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;        &lt;span class=&quot;function-call&quot;&gt;supervisorctl&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;restart&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span class=&quot;keyword-conditional&quot;&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span class=&quot;keyword-repeat&quot;&gt;done&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;How it works&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;supervisorctl status&lt;/code&gt; lists all daemons with their current PIDs.&lt;/li&gt;
&lt;li&gt;Non-&lt;code&gt;RUNNING&lt;/code&gt; daemons are skipped (they have no PID to look up).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/proc/&lt;pid&gt;/cwd&lt;/code&gt; resolves the actual working directory for each process.&lt;/li&gt;
&lt;li&gt;Any daemon whose working directory matches the target path (or is a subpath of the target path) gets restarted.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Usage&lt;/h1&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;chmod&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;+x&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;restart-daemons.sh&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;./restart-daemons.sh&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/home/forge/my-app/current&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is especially useful on servers running multiple deployments of the same application — for example, a staging environment with both a &lt;code&gt;current&lt;/code&gt; and a &lt;code&gt;previous&lt;/code&gt; release still running workers.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/laravel&quot;&gt;#laravel&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/terminal&quot;&gt;#terminal&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/linux&quot;&gt;#linux&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sysadmin&quot;&gt;#sysadmin&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-11T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/restarting-supervisord-daemons-by-working-directory-on-linux</id>
    <title>🐥 Restarting supervisord daemons by working directory on Linux</title>
    <updated>2026-05-11T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/shell-tricks-that-actually-make-life-easier-and-save-your-sanity" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;The post is a practical roundup of shell shortcuts and habits that make command-line work faster and less frustrating. It highlights useful editing, navigation, and safety tips for Bash/Zsh users, plus a few portable tricks that work across POSIX shells as well.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://blog.hofstede.it/shell-tricks-that-actually-make-life-easier-and-save-your-sanity/&quot;&gt;Continue reading on &lt;strong&gt;blog.hofstede.it&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/terminal&quot;&gt;#terminal&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-11T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/shell-tricks-that-actually-make-life-easier-and-save-your-sanity</id>
    <title>🔗 Shell tricks that actually make life easier (and save your sanity)</title>
    <updated>2026-05-11T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/anti-frameworkism-choosing-native-web-apis-over-frameworks" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Today’s browsers can handle most issues that frontend frameworks were created to solve. Developers often still opt for frameworks like React, Angular, or Vue, which can increase page weight, hurt performance, and affect SEO despite native web APIs being sufficient for many tasks.&lt;/p&gt;
&lt;p&gt;This article identifies the disparity between &quot;frameworkism&quot; and &quot;anti-frameworkism.&quot; It emphasizes that frameworks are not always necessary and advocates for a native API-first approach where developers rely on native browser capabilities before turning to frameworks.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://blog.logrocket.com/anti-frameworkism-native-web-apis&quot;&gt;Continue reading on &lt;strong&gt;blog.logrocket.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/javascript&quot;&gt;#javascript&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/frontend&quot;&gt;#frontend&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/html&quot;&gt;#html&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-11T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/anti-frameworkism-choosing-native-web-apis-over-frameworks</id>
    <title>🔗 Anti-frameworkism: Choosing native web APIs over frameworks</title>
    <updated>2026-05-11T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/laravel-raised-money-and-now-injects-ads-directly-into-your-agent" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Did Laravel really need to change their agent documentation to push Laravel Cloud? After raising a $57M Series A, it&apos;s clear they want to monetize effectively. The latest PR suggests agents should default to Laravel Cloud for deployments, sparking complaints from users about the perceived bias towards Laravel&apos;s commercial platform. This shift raises questions about the erosion of community trust and the balance between monetization and quality support. Interestingly, many still recommend Laravel Cloud without the need for forceful promotion. However, it prompts a broader discussion about how ads within development tools might impact user experience and community sentiment. If Laravel&apos;s commercial segment is doing well, why risk alienating devoted users?&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://techstackups.com/articles/laravel-raised-money-and-now-injects-ads-directly-into-your-agent/&quot;&gt;Continue reading on &lt;strong&gt;techstackups.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/laravel&quot;&gt;#laravel&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/announcement&quot;&gt;#announcement&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-10T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/laravel-raised-money-and-now-injects-ads-directly-into-your-agent</id>
    <title>🔗 Laravel raised money and now injects ads directly into your agent</title>
    <updated>2026-05-10T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/migrating-from-digitalocean-to-hetzner-from-1-432-to-233-month-with-zero-downtime" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;I saved over $1,200 per month by migrating from DigitalOcean to Hetzner. Previously, I paid $1,432/month for a droplet, while the Hetzner AX162-R costs only $233/month. Managing 248 GB of MySQL data across 30 databases and hosting 34 Nginx sites meant careful planning to avoid downtime. I implemented a six-phase strategy that included live MySQL replication using &lt;code&gt;mydumper&lt;/code&gt; for fast data export and loading, and a well-scripted DNS cutover that reduced TTL to allow for quick switching. After the move, I noticed significant performance improvements thanks to MySQL 8.0&apos;s enhancements. The process involved meticulous configuration of services and a thorough testing phase before the final switch-over. The transition was seamless, with all traffic constantly routed during migration, solidifying the importance of planning when moving critical infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://isayeter.com/posts/digitalocean-to-hetzner-migration/&quot;&gt;Continue reading on &lt;strong&gt;isayeter.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/mysql&quot;&gt;#mysql&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/linux&quot;&gt;#linux&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-10T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/migrating-from-digitalocean-to-hetzner-from-1-432-to-233-month-with-zero-downtime</id>
    <title>🔗 Migrating from DigitalOcean to Hetzner: from $1,432 to $233/month with zero downtime</title>
    <updated>2026-05-10T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/pg-textsearch-1-0-how-we-built-a-bm25-search-engine-on-postgres-pages" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Building a BM25 search engine on Postgres required addressing significant limitations of the built-in &lt;code&gt;ts_rank&lt;/code&gt;. As I learned, the lack of inverse document frequency and term frequency saturation affected the relevance and scalability of searches. By creating &lt;code&gt;pg_textsearch&lt;/code&gt;, they implemented real BM25 scoring via a native indexing solution in C integrated with Postgres.&lt;/p&gt;
&lt;p&gt;The design included a hybrid architecture with a write-optimized memtable and immutable disk segments. Notably, they achieved fast query performance through optimizations like Block-Max WAND, allowing for much quicker retrieval of top results without scoring every match. With extensive benchmarks, they showed &lt;code&gt;pg_textsearch&lt;/code&gt; can outperform existing tools like ParadeDB, achieving up to 6.5x speed increases on 138 million document queries. This combination of efficiency and native integration makes it a compelling option for teams needing robust search capabilities within Postgres.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://www.tigerdata.com/blog/pg-textsearch-bm25-full-text-search-postgres&quot;&gt;Continue reading on &lt;strong&gt;www.tigerdata.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/postgresql&quot;&gt;#postgresql&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-10T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/pg-textsearch-1-0-how-we-built-a-bm25-search-engine-on-postgres-pages</id>
    <title>🔗 pg_textsearch: How we built a BM25 search engine on Postgres pages</title>
    <updated>2026-05-10T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/make-your-shell-scripts-environment-variables-overridable" rel="alternate"/>
    <content type="html">&lt;p&gt;When writing shell scripts, hardcoded values are a common source of friction. A script that works perfectly on your machine may need adjustments on a colleague&apos;s setup, in CI, or in production — and forcing people to edit the script itself is messy.&lt;/p&gt;
&lt;p&gt;There&apos;s a simple Bash idiom that solves this cleanly.&lt;/p&gt;
&lt;h1&gt;The Problem&lt;/h1&gt;
&lt;p&gt;Consider a typical startup script:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;8888&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;0.0.0.0&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;uvicorn&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;myapp:app&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--host&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--port&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The port is hardcoded. If you need to run two instances, or if &lt;code&gt;8888&lt;/code&gt; is already taken, you have to edit the file.&lt;/p&gt;
&lt;h1&gt;The Fix: &lt;code&gt;$&amp;lbrace;VAR:-default&amp;rbrace;&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Replace the hardcoded assignment with a default-value expansion:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;8888&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;0.0.0.0&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;uvicorn&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;myapp:app&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--host&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--port&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The syntax &lt;code&gt;$&amp;lbrace;VAR:-default&amp;rbrace;&lt;/code&gt; means:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Use the value of &lt;code&gt;$VAR&lt;/code&gt; if it is set and non-empty; otherwise use &lt;code&gt;default&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The script&apos;s behaviour is unchanged when no environment variable is provided — the default kicks in. But now callers can override it without touching the file:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;9000&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;./bin/start.sh&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or by exporting it beforehand:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;keyword-import&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;9000&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;./bin/start.sh&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Why This Matters&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No edits required.&lt;/strong&gt; The script stays clean; overrides happen at call time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI-friendly.&lt;/strong&gt; Most CI systems let you set environment variables per job. Your script picks them up automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-documenting.&lt;/strong&gt; The default value lives right next to the variable name, so readers immediately know what to expect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composable.&lt;/strong&gt; You can layer overrides: a &lt;code&gt;.env&lt;/code&gt; file, a CI variable, or a one-liner prefix — whatever fits the context.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Variants Worth Knowing&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&amp;lbrace;VAR:-default&amp;rbrace;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;default&lt;/code&gt; if &lt;code&gt;VAR&lt;/code&gt; is unset &lt;strong&gt;or empty&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&amp;lbrace;VAR-default&amp;rbrace;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;default&lt;/code&gt; only if &lt;code&gt;VAR&lt;/code&gt; is unset (empty string is kept)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&amp;lbrace;VAR:=default&amp;rbrace;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;default&lt;/code&gt; and &lt;strong&gt;assign it back&lt;/strong&gt; to &lt;code&gt;VAR&lt;/code&gt; if unset or empty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&amp;lbrace;VAR:?message&amp;rbrace;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Abort with &lt;code&gt;message&lt;/code&gt; if &lt;code&gt;VAR&lt;/code&gt; is unset or empty&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For most cases, &lt;code&gt;$&amp;lbrace;VAR:-default&amp;rbrace;&lt;/code&gt; is the right choice. The &lt;code&gt;:=&lt;/code&gt; variant is useful when you want the variable available for the rest of the script without repeating the default.&lt;/p&gt;
&lt;h1&gt;A Real-World Example&lt;/h1&gt;
&lt;p&gt;Here&apos;s a before/after for a uvicorn startup script:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;0.0.0.0&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;8888&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;uvicorn&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;docai.api.app:app&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--host&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--port&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;0.0.0.0&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;text&quot;&gt;8888&lt;/span&gt;&lt;span class=&quot;punctuation-special&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;uvicorn&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;docai.api.app:app&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--host&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;HOST&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--port&lt;/span&gt; &lt;span class=&quot;punctuation-special&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;PORT&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two characters added per line (&lt;code&gt;$&amp;lbrace;&lt;/code&gt; and &lt;code&gt;:-&lt;/code&gt;), zero behaviour change by default, and now fully overridable from the outside. A small habit with outsized payoff.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/terminal&quot;&gt;#terminal&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-09T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/make-your-shell-scripts-environment-variables-overridable</id>
    <title>🐥 Make your shell scripts&apos; environment variables overridable</title>
    <updated>2026-05-09T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/tidewave-changed-how-i-feel-about-ai-assisted-coding" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;The author initially disliked AI coding assistants, finding inline autocomplete intrusive and distracting from their work. After exploring GitHub Copilot Pro agents in Zed, they realized the core problem: AI agents operate at the wrong abstraction layer, viewing static code rather than the running application.&lt;/p&gt;
&lt;p&gt;Tidewave, an Elixir-based tool by José Valim, runs directly inside applications at runtime and lets agents see what developers see. The author&apos;s workflow involves connecting Tidewave to Copilot, using Gemini 3.1 Pro to implement features while watching the agent test changes in the browser, then manually refactoring the generated code in Zed before committing.&lt;/p&gt;
&lt;p&gt;This approach resolves the abstraction layer problem and keeps the developer engaged with their codebase. The author maintains manual control over code quality and structure, avoiding the pitfalls of fully autonomous agents while still leveraging AI&apos;s capabilities effectively.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://fabrithedev.com/posts/2026/3/tidewave-is-game-changer?locale=nl&quot;&gt;Continue reading on &lt;strong&gt;fabrithedev.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-09T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/tidewave-changed-how-i-feel-about-ai-assisted-coding</id>
    <title>🔗 Finding my way with AI coding agents</title>
    <updated>2026-05-09T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/every-layer-of-review-makes-you-10x-slower" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Every layer of approval in a process makes it approximately 10x slower in wall-clock time, primarily due to waiting and coordination overhead. A simple bug fix takes 30 minutes to code, 5 hours to peer review, a week for design approval, and 12 weeks if another team must schedule it.&lt;/p&gt;
&lt;p&gt;Sustainable speed improvement requires fewer reviews rather than faster reviewers, as AI cannot overcome latency bottlenecks in approval pipelines. Quality improvement should focus on system-wide engineering practices and trust, following Deming&apos;s philosophy, rather than adding more QA layers that create perverse incentives and reduce actual quality.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://apenwarr.ca/log/20260316&quot;&gt;Continue reading on &lt;strong&gt;apenwarr.ca&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-09T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/every-layer-of-review-makes-you-10x-slower</id>
    <title>🔗 Every layer of review makes you 10x slower</title>
    <updated>2026-05-09T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/explains-other-superpowers" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Did you know that the &lt;code&gt;EXPLAIN&lt;/code&gt; command in PostgreSQL has several lesser-known options that can significantly enhance your troubleshooting capabilities? The &lt;code&gt;BUFFERS&lt;/code&gt; option shows where your data was sourced—whether from shared buffers or disk—helping you diagnose cache issues or memory constraints. You can then track memory usage during query planning with &lt;code&gt;MEMORY&lt;/code&gt;. For write-heavy operations, the &lt;code&gt;WAL&lt;/code&gt; option reveals detailed logging metrics, useful for bulk loads or updates. Want to replicate your environment? The &lt;code&gt;SETTINGS&lt;/code&gt; option outlines your configuration settings, while the &lt;code&gt;VERBOSE&lt;/code&gt; flag provides an expanded view of the query planner&apos;s operations. Combine these options, like &lt;code&gt;EXPLAIN (ANALYZE, BUFFERS, WAL, SETTINGS)&lt;/code&gt;, for a comprehensive picture of your query execution and database health.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://richyen.com/postgres/2026/03/23/explain_options.html&quot;&gt;Continue reading on &lt;strong&gt;richyen.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/postgresql&quot;&gt;#postgresql&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-08T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/explains-other-superpowers</id>
    <title>🔗 EXPLAIN&apos;s other superpowers</title>
    <updated>2026-05-08T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/thinking-elixir-podcast-302-beam-in-your-pocket" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;News includes Mob bringing BEAM-on-device native mobile dev to Elixir, Folio for print-quality PDFs via Rustler NIF, Oban v2.22 &amp; Pro v1.7.0 released, LiveVue v1.1 with Node-less SSR, and more!&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://podcast.thinkingelixir.com/302&quot;&gt;Continue reading on &lt;strong&gt;podcast.thinkingelixir.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-08T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/thinking-elixir-podcast-302-beam-in-your-pocket</id>
    <title>🔗 Thinking Elixir Podcast 302: BEAM in Your Pocket</title>
    <updated>2026-05-08T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/most-devs-ignore-git-worktree-heres-why-theyre-wrong" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;I used to dread the inefficiency of branch switching in Git. Switching branches often disrupted my workflow, forcing me to stash changes and lose my train of thought. Then I discovered &lt;code&gt;git worktree&lt;/code&gt;, which transformed my experience by allowing multiple clean working directories for the same repository. With &lt;code&gt;git worktree add&lt;/code&gt;, I could create a new folder for a branch, eliminating the need to stash or lose context. This approach enables parallel development, so I could run tests in one directory while coding in another, minimizing context switches. Yet, many developers ignore it, thinking it’s only for advanced users, even as they spend hours on outdated workflows. After using &lt;code&gt;git worktree&lt;/code&gt;, I could never go back. It’s not flashy, but it’s incredibly effective.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://dev.ongoro.top/post/most-devs-ignore-git-worktree-heres-why-theyre-wrong&quot;&gt;Continue reading on &lt;strong&gt;dev.ongoro.top&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/git&quot;&gt;#git&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-08T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/most-devs-ignore-git-worktree-heres-why-theyre-wrong</id>
    <title>🔗 Most devs ignore git worktree. Here&apos;s why they&apos;re wrong</title>
    <updated>2026-05-08T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/using-generators-for-phpunit-data-providers" rel="alternate"/>
    <content type="html">&lt;p&gt;When writing PHPUnit tests, data providers are one of the simplest ways to increase coverage without duplicating test logic. Most examples use arrays, but PHP generators (&lt;code&gt;yield&lt;/code&gt;) are often a better fit: they’re more expressive, memory-efficient, and easier to extend.&lt;/p&gt;
&lt;p&gt;This post walks through a clean, generic approach to using generators for PHPUnit data providers.&lt;/p&gt;
&lt;h1&gt;Why use generators instead of arrays?&lt;/h1&gt;
&lt;p&gt;A traditional data provider might look like this:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;provideCases&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;array&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        &lt;span class=&quot;string&quot;&gt;&amp;#39;case A&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;input-a&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;expected-a&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span class=&quot;string&quot;&gt;&amp;#39;case B&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;input-b&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;expected-b&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but generators give you a few advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No need to build a full array in memory&lt;/li&gt;
&lt;li&gt;Named cases are more natural with &lt;code&gt;yield&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Easier to compose or split into smaller logical chunks&lt;/li&gt;
&lt;li&gt;Better readability when cases grow&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;A simple example&lt;/h1&gt;
&lt;p&gt;Imagine you’re testing a service that transforms input values:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword-type&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;ValueTransformer&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function-method&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;strtoupper&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of writing multiple test methods, you can use a generator-based data provider:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-import&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;PHPUnit&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Framework&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Attributes&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;DataProvider&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;keyword-import&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;module&quot;&gt;PHPUnit&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Framework&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;module&quot;&gt;Attributes&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword-type&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;ValueTransformerTest&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;TestCase&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;#[&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;#[&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;DataProvider&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;provideTransformCases&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function-method&quot;&gt;it_transforms_values&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$input&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$expected&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;void&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span class=&quot;variable&quot;&gt;$service&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;constructor&quot;&gt;ValueTransformer&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;        &lt;span class=&quot;variable&quot;&gt;$result&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$service&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$input&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        &lt;span class=&quot;variable-builtin&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;assertSame&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$expected&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$result&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function-method&quot;&gt;provideTransformCases&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;lowercase&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;hello&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;HELLO&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;mixed case&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;HeLLo&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;HELLO&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;already uppercase&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;WORLD&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;WORLD&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Making test cases more expressive&lt;/h1&gt;
&lt;p&gt;Generators really shine when your inputs are objects instead of primitives.&lt;/p&gt;
&lt;p&gt;For example, testing a query modifier:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword-type&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;QueryModifier&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function-method&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;QueryBuilder&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;QueryBuilder&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span class=&quot;keyword-return&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;orderBy&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your test can focus on behavior while the generator defines the variations:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;#[&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;#[&lt;/span&gt;&lt;span class=&quot;attribute&quot;&gt;DataProvider&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;provideSortingCases&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;it_applies_sorting&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;type-builtin&quot;&gt;void&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;$query&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;Mockery&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;QueryBuilder&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;constant&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;shouldReceive&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;orderBy&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;once&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;andReturnSelf&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;$modifier&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;constructor&quot;&gt;QueryModifier&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span class=&quot;variable&quot;&gt;$modifier&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;function-method-call&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;variable&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$field&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;$direction&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;provideSortingCases&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;sort by name&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;asc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;sort by created_at desc&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;created_at&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;desc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;sort by nested field&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;user.email&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;asc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test stays minimal, while the generator clearly documents the supported scenarios.&lt;/p&gt;
&lt;h1&gt;Composing generators&lt;/h1&gt;
&lt;p&gt;One underrated benefit is that generators can be composed. You can split cases into smaller methods and reuse them:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-php&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;provideSortingCases&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield from&lt;/span&gt; &lt;span class=&quot;variable-builtin&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;basicFields&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield from&lt;/span&gt; &lt;span class=&quot;variable-builtin&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;function-call&quot;&gt;nestedFields&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;basicFields&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;name asc&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;asc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;created_at desc&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;created_at&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;desc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span class=&quot;keyword-modifier&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword-modifier&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword-function&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;nestedFields&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;punctuation-delimiter&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;type&quot;&gt;Generator&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span class=&quot;keyword-return&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;user email&amp;#39;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;punctuation-bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;#39;user.email&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;#39;asc&amp;#39;&lt;/span&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;punctuation-delimiter&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span class=&quot;punctuation-bracket&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps large test suites maintainable without sacrificing clarity.&lt;/p&gt;
&lt;h1&gt;When to prefer generators&lt;/h1&gt;
&lt;p&gt;Generators are especially useful when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have many test cases&lt;/li&gt;
&lt;li&gt;Test data is constructed dynamically&lt;/li&gt;
&lt;li&gt;You want to group or reuse subsets of cases&lt;/li&gt;
&lt;li&gt;Memory usage might become a concern&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For small, static datasets, arrays are perfectly fine. But once your data providers grow, generators tend to scale much better.&lt;/p&gt;
&lt;h1&gt;Final thoughts&lt;/h1&gt;
&lt;p&gt;Using generators in PHPUnit data providers is a small change that improves readability and flexibility. Tests become easier to extend, and the intent of each case is clearer thanks to named yields.&lt;/p&gt;
&lt;p&gt;If you’re already relying on data providers, switching from arrays to generators is a low-effort improvement that pays off quickly in larger test suites.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/php&quot;&gt;#php&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/testing&quot;&gt;#testing&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-07T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/using-generators-for-phpunit-data-providers</id>
    <title>🐥 Using generators for PHPUnit data providers</title>
    <updated>2026-05-07T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/php-attributes-in-laravel-13-the-ultimate-guide-36-new-attributes" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;New Laravel 13 went all-in on &lt;a href=&quot;https://www.php.net/manual/en/language.attributes.overview.php&quot;&gt;PHP attributes&lt;/a&gt;. Properties like &lt;code&gt;$fillable&lt;/code&gt;, &lt;code&gt;$guarded&lt;/code&gt;, and &lt;code&gt;$hidden&lt;/code&gt; that you&apos;ve been defining on models for years can now be declared as class-level attributes: &lt;code&gt;#[Fillable]&lt;/code&gt;, &lt;code&gt;#[Guarded]&lt;/code&gt;, and &lt;code&gt;#[Hidden]&lt;/code&gt;. The same applies to job configuration, console command signatures, middleware, and more.&lt;/p&gt;
&lt;p&gt;In this article, I&apos;ll walk you through &lt;strong&gt;every PHP attribute&lt;/strong&gt; available in Laravel 13 — both the new ones and those that existed before — with practical code examples for each.&lt;/p&gt;
&lt;p&gt;Important notice: those attributes are &lt;strong&gt;optional&lt;/strong&gt;, and you may use the old syntax in all those cases. The attributes are NOT breaking changes, just the new alternatives.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://laraveldaily.com/post/php-attributes-in-laravel-13-the-ultimate-guide-36-new-attributes&quot;&gt;Continue reading on &lt;strong&gt;laraveldaily.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/laravel&quot;&gt;#laravel&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/php&quot;&gt;#php&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-07T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/php-attributes-in-laravel-13-the-ultimate-guide-36-new-attributes</id>
    <title>🔗 PHP attributes in Laravel 13: The ultimate guide (36 new attributes!)</title>
    <updated>2026-05-07T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/debugging-slow-ecto-queries-with-appsignal" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;A sports car can only be driven as fast as the road it&apos;s on. In Phoenix applications, the database often becomes that bottleneck, impacting performance.  AppSignal aids in detecting slow Ecto queries by auto-instrumenting them. It allows developers to pinpoint the specific queries causing performance issues. The tool highlights slow queries in the performance section, showing them ranked by impact based on frequency and duration. To resolve issues, ensuring tables are indexed properly and avoiding unbounded queries are key strategies. For common problems like N+1 queries, employing techniques such as limiting query results or offloading work to materialized views can greatly enhance performance. After making adjustments, it’s crucial to verify that these changes effectively reduce latency by checking AppSignal&apos;s insights post-deployment.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://blog.appsignal.com/2026/04/02/debugging-slow-ecto-queries-with-appsignal&quot;&gt;Continue reading on &lt;strong&gt;blog.appsignal.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/database&quot;&gt;#database&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/tools&quot;&gt;#tools&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-07T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/debugging-slow-ecto-queries-with-appsignal</id>
    <title>🔗 Debugging slow ecto queries with AppSignal</title>
    <updated>2026-05-07T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/did-contexts-kill-phoenix" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;The author reflects on the challenges faced by the Phoenix framework, particularly how the introduction of contexts may have hindered its adoption among new developers.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://arrowsmithlabs.com/blog/did-contexts-kill-phoenix&quot;&gt;Continue reading on &lt;strong&gt;arrowsmithlabs.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/elixir&quot;&gt;#elixir&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/phoenix&quot;&gt;#phoenix&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-06T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/did-contexts-kill-phoenix</id>
    <title>🔗 Did contexts kill Phoenix?</title>
    <updated>2026-05-06T17:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/on-knowing-what-code-to-throw-away" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;This article discusses a shift in coding practices due to the emergence of agentic coding tools. The emphasis has moved from merely writing code to knowing what to remove, as the ability to generate code has become abundant while the skill of effective curation remains scarce.&lt;/p&gt;
&lt;p&gt;The article compares this change to Michelangelo&apos;s sculpting process, where the focus is on revealing the form within the marble rather than adding more material. As a result, software engineers now face the challenge of discerning which elements are essential, recognizing that the skill of editing is more crucial than the act of building itself.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://matthewsinclair.com/blog/0188-on-knowing-what-code-to-throw-away&quot;&gt;Continue reading on &lt;strong&gt;matthewsinclair.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/development&quot;&gt;#development&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/ai&quot;&gt;#ai&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-06T13:00:00Z</published>
    <id>https://www.yellowduck.be/posts/on-knowing-what-code-to-throw-away</id>
    <title>🔗 On knowing what code to throw away</title>
    <updated>2026-05-06T13:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/what-i-learned-from-nearly-1-000-interviews-at-amazon" rel="alternate"/>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;I spent over a decade as an Amazon Bar Raiser, interviewing nearly 1,000 candidates across all levels. A surprising trend emerged: technical skills mattered less than how candidates presented themselves. Many who excelled technically still failed due to poor storytelling in behavioral interviews. While preparation for technical skills is concrete and quantifiable, non-technical preparation can be neglected entirely. I found that spending even a small portion of your prep time on crafting clear, concise personal stories can dramatically improve your interview performance. Ultimately, interviews should be viewed as auditions for teamwork rather than exams to be passed, emphasizing the importance of fit and personality in hiring decisions.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://alifeengineered.substack.com/p/what-i-learned-from-nearly-1000-interviews&quot;&gt;Continue reading on &lt;strong&gt;alifeengineered.substack.com&lt;/strong&gt;&lt;/a&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/reading-list&quot;&gt;#reading-list&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-06T08:00:00Z</published>
    <id>https://www.yellowduck.be/posts/what-i-learned-from-nearly-1-000-interviews-at-amazon</id>
    <title>🔗 What I learned from nearly 1,000 interviews at Amazon</title>
    <updated>2026-05-06T08:00:00Z</updated>
  </entry>
  <entry>
    <author>
      <name>Pieter Claerhout</name>
      <email>pieter@yellowduck.be</email>
    </author>
    <link href="https://www.yellowduck.be/posts/fixing-the-ondrej-nginx-ppa-403-error-on-laravel-forge-servers" rel="alternate"/>
    <content type="html">&lt;p&gt;If you&apos;re running Ubuntu 22.04 on a Laravel Forge-managed server, you may have recently started seeing this error during &lt;code&gt;apt update&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Err:7 https://ppa.launchpadcontent.net/ondrej/nginx/ubuntu jammy InRelease
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  403  Forbidden
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;E: The repository &amp;#39;https://ppa.launchpadcontent.net/ondrej/nginx/ubuntu jammy InRelease&amp;#39; is no longer signed.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;What&apos;s happening?&lt;/h1&gt;
&lt;p&gt;Forge historically added the &lt;code&gt;ondrej/nginx&lt;/code&gt; PPA to its servers to get newer versions of nginx. Ondrej Surý (the maintainer) has since moved this PPA to a &lt;strong&gt;paid subscription model&lt;/strong&gt;, so unauthenticated access now returns a 403. Your server still has the old PPA entry, but it can no longer reach it.&lt;/p&gt;
&lt;p&gt;You&apos;ll notice the &lt;code&gt;ondrej/php&lt;/code&gt; PPA still works fine — that one remains freely available.&lt;/p&gt;
&lt;h1&gt;The fix&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Find the file that references the broken PPA:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;ondrej/nginx&quot;&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/etc/apt/sources.list.d/&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Remove it (the filename will match what grep returned above):&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/etc/apt/sources.list.d/ondrej-ubuntu-nginx-jammy.list&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/etc/apt/sources.list.d/ondrej-ubuntu-nginx-jammy.list.distUpgrade&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: &lt;code&gt;add-apt-repository --remove ppa:ondrej/nginx&lt;/code&gt; won&apos;t work here — Launchpad returns a 403 before it can resolve the PPA name.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Run &lt;code&gt;apt update&lt;/code&gt; again. If you need a newer version of nginx than Ubuntu&apos;s default (1.18.x), add the official nginx.org repository instead:&lt;/p&gt;
&lt;pre class=&quot;lumis&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-fsSL&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;https://nginx.org/keys/nginx_signing.key&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;gpg&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;--dearmor&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/usr/share/keyrings/nginx-archive-keyring.gpg&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span class=&quot;function-builtin&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&quot;deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu jammy nginx&quot;&lt;/span&gt;&lt;span class=&quot;text&quot;&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;tee&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;/etc/apt/sources.list.d/nginx.list&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;update&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span class=&quot;function-call&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;variable-parameter&quot;&gt;nginx&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Do you need a newer nginx?&lt;/h1&gt;
&lt;p&gt;For most Laravel applications, Ubuntu&apos;s default nginx is perfectly adequate. Unless you have a specific reason to run a cutting-edge nginx version, removing the PPA and sticking with the default is the simplest and most stable path.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.yellowduck.be/tags/laravel&quot;&gt;#laravel&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/best-practice&quot;&gt;#best-practice&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/devops&quot;&gt;#devops&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/linux&quot;&gt;#linux&lt;/a&gt; &lt;a href=&quot;https://www.yellowduck.be/tags/sysadmin&quot;&gt;#sysadmin&lt;/a&gt;&lt;/p&gt;</content>
    <published>2026-05-05T17:00:00Z</published>
    <id>https://www.yellowduck.be/posts/fixing-the-ondrej-nginx-ppa-403-error-on-laravel-forge-servers</id>
    <title>🐥 Fixing the Ondrej Nginx PPA 403 error on Laravel Forge servers</title>
    <updated>2026-05-05T17:00:00Z</updated>
  </entry>
</feed>