<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Williams Ashibuogwu</title>
    <description>The latest articles on DEV Community by Williams Ashibuogwu (@wbizmo).</description>
    <link>https://dev.to/wbizmo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4110384%2Fa0d98bc6-40a2-4e24-b4e6-9cb5d1000f79.png</url>
      <title>DEV Community: Williams Ashibuogwu</title>
      <link>https://dev.to/wbizmo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wbizmo"/>
    <language>en</language>
    <item>
      <title>The Happy Path Is Lying to You: What Production Systems Taught Me About Idempotency, Reversals, and Financial Truth</title>
      <dc:creator>Williams Ashibuogwu</dc:creator>
      <pubDate>Wed, 07 Oct 2026 21:08:24 +0000</pubDate>
      <link>https://dev.to/wbizmo/the-happy-path-is-lying-to-you-what-production-systems-taught-me-about-idempotency-reversals-and-85p</link>
      <guid>https://dev.to/wbizmo/the-happy-path-is-lying-to-you-what-production-systems-taught-me-about-idempotency-reversals-and-85p</guid>
      <description>&lt;p&gt;There is a point where a software project stops being "an app."&lt;/p&gt;

&lt;p&gt;The UI might still look the same.&lt;/p&gt;

&lt;p&gt;There are forms, tables, buttons, APIs, and database rows.&lt;/p&gt;

&lt;p&gt;But suddenly a bug does not mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The page displayed the wrong thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The system charged twice.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The accounting entry was duplicated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Someone reversed a transaction they were not authorized to touch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The balance shown to the user was stale, but the system treated it as authoritative.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That changes the way you have to build software.&lt;/p&gt;

&lt;p&gt;Over the last few weeks, I have been working on two production systems from very different domains.&lt;/p&gt;

&lt;p&gt;One handles business operations such as sales, inventory, accounting, reporting, staff permissions, and branch activity.&lt;/p&gt;

&lt;p&gt;The other handles accounts, balances, transfers, pricing, reconciliation, and asynchronous financial events.&lt;/p&gt;

&lt;p&gt;Different products.&lt;/p&gt;

&lt;p&gt;Same engineering problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once software starts carrying real consequences, the main job is no longer creating features.&lt;/p&gt;

&lt;p&gt;The job is preserving truth when the system is under pressure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post is about some of the patterns that become important at that point.&lt;/p&gt;




&lt;h2&gt;
  
  
  CRUD works until your rows start meaning something
&lt;/h2&gt;

&lt;p&gt;A typical application starts simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create
Read
Update
Delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You build a few resources.&lt;/p&gt;

&lt;p&gt;Then you add authentication.&lt;/p&gt;

&lt;p&gt;Then permissions.&lt;/p&gt;

&lt;p&gt;Then reports.&lt;/p&gt;

&lt;p&gt;Then background jobs.&lt;/p&gt;

&lt;p&gt;Then integrations.&lt;/p&gt;

&lt;p&gt;Eventually, one of those database rows starts representing something that matters outside the database.&lt;/p&gt;

&lt;p&gt;A stock movement affects whether an item can be sold.&lt;/p&gt;

&lt;p&gt;A payment affects receivables.&lt;/p&gt;

&lt;p&gt;A journal entry changes financial statements.&lt;/p&gt;

&lt;p&gt;A transfer affects someone's available money.&lt;/p&gt;

&lt;p&gt;At that point, this becomes dangerous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;125000&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because you may have just changed history.&lt;/p&gt;

&lt;p&gt;That transaction might already have affected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an account balance&lt;/li&gt;
&lt;li&gt;a journal&lt;/li&gt;
&lt;li&gt;a report&lt;/li&gt;
&lt;li&gt;a notification&lt;/li&gt;
&lt;li&gt;an external transfer&lt;/li&gt;
&lt;li&gt;a reconciliation process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system now needs something stronger than CRUD.&lt;/p&gt;

&lt;p&gt;It needs &lt;strong&gt;invariants&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Invariants are the real feature
&lt;/h2&gt;

&lt;p&gt;For a manual accounting entry, the screen can be simple.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Account&lt;/th&gt;
&lt;th&gt;Debit&lt;/th&gt;
&lt;th&gt;Credit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash&lt;/td&gt;
&lt;td&gt;50,000&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sales Revenue&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;50,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The hard part is not rendering those inputs.&lt;/p&gt;

&lt;p&gt;The hard part is guaranteeing that the data can never become financially impossible.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateJournal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JournalLine&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A journal requires at least two lines&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;debit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;credit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasDebit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debit&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasCredit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credit&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasDebit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;hasCredit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Each line must contain either a debit or a credit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;debit&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;credit&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;debit&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;debit&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Journal is not balanced&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That validation is useful.&lt;/p&gt;

&lt;p&gt;It is not enough.&lt;/p&gt;

&lt;p&gt;The system still needs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the account active?&lt;/li&gt;
&lt;li&gt;Can this account receive manual postings?&lt;/li&gt;
&lt;li&gt;Is the accounting period open?&lt;/li&gt;
&lt;li&gt;Does the user belong to this branch?&lt;/li&gt;
&lt;li&gt;Has this exact operation already been posted?&lt;/li&gt;
&lt;li&gt;Did the account state change after the form loaded?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last question is where things get interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validate again at commit time
&lt;/h2&gt;

&lt;p&gt;Imagine a finance user opens a posting screen at 2:00 PM.&lt;/p&gt;

&lt;p&gt;The accounting period is open.&lt;/p&gt;

&lt;p&gt;They prepare a long journal.&lt;/p&gt;

&lt;p&gt;At 2:05 PM, an administrator closes the accounting period.&lt;/p&gt;

&lt;p&gt;At 2:07 PM, the user clicks &lt;strong&gt;Post&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the application only validated the period when the form loaded, you now have a posting inside a closed period.&lt;/p&gt;

&lt;p&gt;The rule needs to be enforced inside the transaction that performs the write.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;period&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountingPeriods&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;periodId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forUpdate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;period&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;period&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OPEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Accounting period is closed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounts&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whereIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forUpdate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;manualPostingAllowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Account cannot accept this posting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createJournal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important idea is not the syntax.&lt;/p&gt;

&lt;p&gt;It is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A rule that matters financially should be checked as close as possible to the write that makes the operation permanent.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The check-then-insert race
&lt;/h2&gt;

&lt;p&gt;Here is one of my favorite production bugs because the code often looks completely reasonable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findFirst&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sourceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sourceEvent&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sourceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sourceEvent&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks fine.&lt;/p&gt;

&lt;p&gt;Until two requests arrive together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request A
&lt;/h3&gt;

&lt;p&gt;Checks.&lt;/p&gt;

&lt;p&gt;Nothing exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request B
&lt;/h3&gt;

&lt;p&gt;Checks.&lt;/p&gt;

&lt;p&gt;Nothing exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request A
&lt;/h3&gt;

&lt;p&gt;Inserts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request B
&lt;/h3&gt;

&lt;p&gt;Also tries to insert.&lt;/p&gt;

&lt;p&gt;If the database has no uniqueness constraint, congratulations:&lt;/p&gt;

&lt;p&gt;You now have two financial effects.&lt;/p&gt;

&lt;p&gt;If the database does have a uniqueness constraint, that is much better.&lt;/p&gt;

&lt;p&gt;But Request B may still receive an ugly database exception even though the operation actually succeeded.&lt;/p&gt;

&lt;p&gt;The stronger implementation treats the constraint as part of the control flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sourceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;sourceEvent&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isUniqueViolation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findFirst&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sourceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;sourceEvent&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And at the database layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;journal_source_identity_unique&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;journals&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;source_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;source_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;source_event&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the database decides who wins.&lt;/p&gt;

&lt;p&gt;The application knows how to handle the loser.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Application checks can reduce unnecessary conflicts. Database constraints enforce reality.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Idempotency is not a frontend feature
&lt;/h2&gt;

&lt;p&gt;A common mistake is thinking this solves duplicate operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It helps UX.&lt;/p&gt;

&lt;p&gt;It does not solve duplication.&lt;/p&gt;

&lt;p&gt;A user can still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open two browser tabs&lt;/li&gt;
&lt;li&gt;resend an HTTP request&lt;/li&gt;
&lt;li&gt;refresh after a timeout&lt;/li&gt;
&lt;li&gt;trigger a mobile retry&lt;/li&gt;
&lt;li&gt;cause a queue redelivery&lt;/li&gt;
&lt;li&gt;receive the same webhook twice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A consequential operation should have a stable identity.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operation_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01K7H4N9S9P1D9JYC0H7..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NGN"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then persist that identity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;transfer_operation_unique&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;transfers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retries can now become safe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;transfers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByOperationId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestHash&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConflictError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Operation ID was already used with different data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This gives you two useful behaviors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;same key + same payload
=&amp;gt; return previous result

same key + different payload
=&amp;gt; reject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much stronger than blindly returning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Already exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reversals are better than rewriting history
&lt;/h2&gt;

&lt;p&gt;Suppose a journal entry is wrong.&lt;/p&gt;

&lt;p&gt;The tempting solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;journal_lines&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;debit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9001&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have a clean-looking current state.&lt;/p&gt;

&lt;p&gt;But you lost historical truth.&lt;/p&gt;

&lt;p&gt;A better model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Journal #123
Debit Cash                 100,000
Credit Revenue             100,000

Reversal #456
Credit Cash                100,000
Debit Revenue              100,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original event still exists.&lt;/p&gt;

&lt;p&gt;The correction exists separately.&lt;/p&gt;

&lt;p&gt;They are linked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Journal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;reversedBy&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;reverses&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the reversal command itself should be idempotent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reverseJournal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journals&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forUpdate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NotFoundError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reversedBy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reversedBy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reversal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createOppositeJournal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;original&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;journals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;reversedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reversal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Retrying the reversal should not produce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reverse
reverse
reverse
reverse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should return the one reversal that already exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  Financial history should be append-heavy
&lt;/h2&gt;

&lt;p&gt;This pattern is useful far beyond journals.&lt;/p&gt;

&lt;p&gt;Payments, refunds, voids, settlement corrections, and adjustments can all be represented as related events.&lt;/p&gt;

&lt;p&gt;That makes the system much easier to explain later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does this balance equal ₦42,300?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;/p&gt;

&lt;p&gt;&amp;gt; Because that is the value currently stored in the balance column.&lt;/p&gt;

&lt;p&gt;The system can explain:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Opening balance         +100,000
Purchase                 -35,000
Transfer                 -20,000
Fee                       -1,500
Refund                    +5,000
Adjustment                -6,200
--------------------------------
Current balance           42,300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;That explainability becomes extremely important once support, finance teams, auditors, and users start asking questions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your browser should not decide how much money moves
&lt;/h2&gt;

&lt;p&gt;Suppose a transfer has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Amount:       10,000
Network fee:     100
Service fee:      50
Total debit:  10,150
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not let the client invent the final debit.&lt;/p&gt;

&lt;p&gt;This is weak:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;networkFee&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;serviceFee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/transfer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;total&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why trust &lt;code&gt;total&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;The client is not authoritative.&lt;/p&gt;

&lt;p&gt;A stronger flow looks like this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a quote
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /transfer-quotes
Content-Type: application/json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"destination_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acct_123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quote_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"quote_abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fee_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_debit_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1015000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-07T20:30:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Display the quote
&lt;/h3&gt;

&lt;p&gt;The UI renders what the server calculated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Authorize that exact debit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /transfers
Content-Type: application/json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quote_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"quote_abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"proof"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend verifies the quote itself.&lt;/p&gt;

&lt;p&gt;Not a client-provided total.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The server should always be able to explain exactly what amount the user authorized.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Minor units deserve boring code
&lt;/h2&gt;

&lt;p&gt;Money bugs are often surprisingly boring.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minimum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does that mean?&lt;/p&gt;

&lt;p&gt;₦50,000?&lt;/p&gt;

&lt;p&gt;₦500?&lt;/p&gt;

&lt;p&gt;50,000 kobo?&lt;/p&gt;

&lt;p&gt;Without a convention, the code is ambiguous.&lt;/p&gt;

&lt;p&gt;I prefer names like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minimumMinor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="nx"&gt;_000n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Formatting happens only at the presentation edge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatMoney&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NumberFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-NG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arithmetic stays integer-based:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalDebitMinor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;amountMinor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="nx"&gt;serviceFeeMinor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="nx"&gt;networkFeeMinor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;20.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You do not want floating-point surprises anywhere near money.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reconciliation is not cleanup
&lt;/h2&gt;

&lt;p&gt;External systems introduce an uncomfortable state.&lt;/p&gt;

&lt;p&gt;You send a transfer request.&lt;/p&gt;

&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your request times out.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happened?&lt;/p&gt;

&lt;p&gt;You do not know.&lt;/p&gt;

&lt;p&gt;That is different from failure.&lt;/p&gt;

&lt;p&gt;This can be dangerous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The external system may have accepted the transaction before the connection disappeared.&lt;/p&gt;

&lt;p&gt;Now your local system says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while the external system later says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUCCESS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the user retries.&lt;/p&gt;

&lt;p&gt;The original transfer eventually completes.&lt;/p&gt;

&lt;p&gt;The retry completes too.&lt;/p&gt;

&lt;p&gt;A better state model includes uncertainty.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TransferStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After an ambiguous timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reconciliation decides the final state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;external&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchTransferStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;externalReference&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;external&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;successful&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;settleExactlyOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;external&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;scheduleAnotherCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;UNKNOWN&lt;/code&gt; is a valid state.&lt;/p&gt;

&lt;p&gt;Pretending uncertainty does not exist is not.&lt;/p&gt;




&lt;h2&gt;
  
  
  Webhooks can arrive twice
&lt;/h2&gt;

&lt;p&gt;Actually, assume they will.&lt;/p&gt;

&lt;p&gt;This is dangerous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;creditAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A webhook sender may retry because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your server returned 500&lt;/li&gt;
&lt;li&gt;the connection closed too early&lt;/li&gt;
&lt;li&gt;they did not receive your acknowledgement&lt;/li&gt;
&lt;li&gt;delivery is intentionally at-least-once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A safer approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhookEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;applyFinancialEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhookEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;processedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus a database constraint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;webhook_event_unique&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;webhook_events&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the database is part of your correctness model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stale data can be worse than no data
&lt;/h2&gt;

&lt;p&gt;One particularly interesting class of bugs happens when the application already has a value.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"balance_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application sees that and assumes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Balance is zero.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that value may have been captured hours ago.&lt;/p&gt;

&lt;p&gt;Meanwhile, a more recent confirmed event says funds exist.&lt;/p&gt;

&lt;p&gt;The local value is not missing.&lt;/p&gt;

&lt;p&gt;It is stale.&lt;/p&gt;

&lt;p&gt;That can be more dangerous because it looks authoritative.&lt;/p&gt;

&lt;p&gt;A better representation carries metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BalanceSnapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;amountMinor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;observedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ledger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;external-confirmation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the application can reason about evidence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chooseBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BalanceSnapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BalanceSnapshot&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;external-confirmation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observedAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observedAt&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real implementation may be more complicated.&lt;/p&gt;

&lt;p&gt;The important lesson is simpler:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Presence does not imply authority.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Authorization belongs inside the domain
&lt;/h2&gt;

&lt;p&gt;A UI might hide a reversal button.&lt;/p&gt;

&lt;p&gt;That is not security.&lt;/p&gt;

&lt;p&gt;This:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canReverse&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Reverse&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is purely presentation.&lt;/p&gt;

&lt;p&gt;The real check must happen server-side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reverseEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;journal.reverse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ForbiddenError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;branchId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;branchId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ForbiddenError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reversalService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that there are two separate questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can this user reverse entries?

Can this user reverse this specific entry?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second one is where many authorization bugs appear.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lock rows in a deterministic order
&lt;/h2&gt;

&lt;p&gt;Concurrency can also create deadlocks.&lt;/p&gt;

&lt;p&gt;Suppose one transaction locks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Account 8
then Account 12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while another locks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Account 12
then Account 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both can wait on each other.&lt;/p&gt;

&lt;p&gt;One useful mitigation is deterministic ordering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountIds&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounts&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whereIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forUpdate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every transaction tries to acquire the same resources in the same order.&lt;/p&gt;

&lt;p&gt;Not every deadlock disappears.&lt;/p&gt;

&lt;p&gt;But you remove an entire class of avoidable ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  UI correctness is still correctness
&lt;/h2&gt;

&lt;p&gt;Not every production bug is deep distributed-systems theory.&lt;/p&gt;

&lt;p&gt;One issue looked approximately like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-code text-xs"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ABC-123
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data existed.&lt;/p&gt;

&lt;p&gt;The backend returned it.&lt;/p&gt;

&lt;p&gt;The test found it in the HTML.&lt;/p&gt;

&lt;p&gt;But an old CSS rule contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.product-row&lt;/span&gt; &lt;span class="nc"&gt;.text-xs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The automated test said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Where is the product code?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both observations were technically correct.&lt;/p&gt;

&lt;p&gt;That is why this test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ABC-123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is not equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ABC-123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the correct layer to test is the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  A passing request does not always mean a successful operation
&lt;/h2&gt;

&lt;p&gt;Another pattern worth separating is transport success from business success.&lt;/p&gt;

&lt;p&gt;Suppose an API call returns HTTP 200:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"accepted"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That might only mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The request was accepted for processing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It may not mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The financial operation is complete.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This distinction should exist in your own models too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OperationState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AUTHORIZED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PROCESSING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SETTLED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Collapsing everything into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;success = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;removes useful information.&lt;/p&gt;

&lt;p&gt;For asynchronous operations, state machines are usually more honest than booleans.&lt;/p&gt;




&lt;h2&gt;
  
  
  Think in commands, not just endpoints
&lt;/h2&gt;

&lt;p&gt;A useful shift for consequential systems is to stop thinking only in HTTP routes.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /journal
POST /transfer
POST /refund
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;think:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostJournal
SubmitTransfer
ReverseJournal
RefundPayment
SettleTransfer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A command can have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an operation identity&lt;/li&gt;
&lt;li&gt;authorization rules&lt;/li&gt;
&lt;li&gt;invariants&lt;/li&gt;
&lt;li&gt;state preconditions&lt;/li&gt;
&lt;li&gt;side effects&lt;/li&gt;
&lt;li&gt;an idempotency policy&lt;/li&gt;
&lt;li&gt;an audit trail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PostJournalCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;actorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;branchId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;postingDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JournalLine&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the command handler becomes the boundary that protects the operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handlePostJournal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PostJournalCommand&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;postJournalExactlyOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP controller becomes much less interesting.&lt;/p&gt;

&lt;p&gt;That is usually a good sign.&lt;/p&gt;




&lt;h2&gt;
  
  
  A useful retry rule
&lt;/h2&gt;

&lt;p&gt;I increasingly think about operations using this simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I execute this again, what should happen?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are only a few acceptable answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read operation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Execute again
=&amp;gt; return current data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Idempotent command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Execute again with same identity
=&amp;gt; return previous result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conflicting retry
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Same identity + different payload
=&amp;gt; reject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explicitly repeatable command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Execute again
=&amp;gt; intentionally create another operation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dangerous answer is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Execute again
=&amp;gt; maybe duplicate something, depends on timing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I now test by default
&lt;/h2&gt;

&lt;p&gt;For consequential features, I increasingly start with hostile questions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if the request arrives twice?&lt;/p&gt;

&lt;p&gt;What if two workers run this simultaneously?&lt;/p&gt;

&lt;p&gt;What if the external API succeeds but our request times out?&lt;/p&gt;

&lt;p&gt;What if the webhook is redelivered?&lt;/p&gt;

&lt;p&gt;What if the accounting period closes while the form is open?&lt;/p&gt;

&lt;p&gt;What if the same idempotency key is reused with different data?&lt;/p&gt;

&lt;p&gt;What if the user belongs to another branch?&lt;/p&gt;

&lt;p&gt;What if the client modifies the fee?&lt;/p&gt;

&lt;p&gt;What if stale local state disagrees with a newer confirmed event?&lt;/p&gt;

&lt;p&gt;What if the correction command is executed twice?&lt;/p&gt;

&lt;p&gt;What if the data exists but the UI hides it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are not exotic edge cases.&lt;/p&gt;

&lt;p&gt;They are normal production behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical checklist
&lt;/h2&gt;

&lt;p&gt;When building something that affects money or operational truth, this is the checklist I increasingly reach for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Is there a stable identity for this operation?

[ ] Can the same request safely execute twice?

[ ] Is uniqueness enforced in the database?

[ ] Are important state checks repeated at commit time?

[ ] Are concurrent writes considered?

[ ] Are locks acquired consistently?

[ ] Can historical financial data be edited?

[ ] Should correction be represented as a reversal instead?

[ ] Is the server authoritative for financial calculations?

[ ] Are money values stored using exact minor units?

[ ] Can asynchronous events be delivered more than once?

[ ] Is UNKNOWN represented separately from FAILED?

[ ] Is there a reconciliation path?

[ ] Can stale state override fresher evidence?

[ ] Are permissions checked server-side?

[ ] Is object, branch, or account scope enforced?

[ ] Can the system explain how the current balance was produced?

[ ] Are user-visible properties tested in the browser?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If several answers are uncomfortable, that is usually where the interesting engineering work begins.&lt;/p&gt;




&lt;h2&gt;
  
  
  The real feature is preserving truth
&lt;/h2&gt;

&lt;p&gt;I used to think production hardening happened after the feature.&lt;/p&gt;

&lt;p&gt;Build the feature.&lt;/p&gt;

&lt;p&gt;Then make it reliable.&lt;/p&gt;

&lt;p&gt;I increasingly think that is the wrong mental model.&lt;/p&gt;

&lt;p&gt;For consequential systems, reliability is part of what the feature means.&lt;/p&gt;

&lt;p&gt;A transfer that succeeds twice is not a working transfer feature.&lt;/p&gt;

&lt;p&gt;A journal entry that can be posted into a closed period is not a working journal feature.&lt;/p&gt;

&lt;p&gt;A balance that cannot explain its own history is not a trustworthy balance.&lt;/p&gt;

&lt;p&gt;A reversal that rewrites history instead of recording correction is not a complete accounting workflow.&lt;/p&gt;

&lt;p&gt;A permission system that only hides buttons is not authorization.&lt;/p&gt;

&lt;p&gt;The implementation is not finished simply because the happy path works.&lt;/p&gt;

&lt;p&gt;It is finished when the system can survive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;retries
timeouts
concurrency
duplicate delivery
stale state
partial failure
correction
unauthorized requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without inventing a new version of the truth.&lt;/p&gt;

&lt;p&gt;That is the part of production engineering I find increasingly interesting.&lt;/p&gt;

&lt;p&gt;Not just making the software work.&lt;/p&gt;

&lt;p&gt;Making sure it continues telling the truth when everything around it gets messy.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>architecture</category>
      <category>fintech</category>
      <category>programming</category>
    </item>
    <item>
      <title>I’m Almost Done Building the Biggest Financial Platform I’ve Worked On</title>
      <dc:creator>Williams Ashibuogwu</dc:creator>
      <pubDate>Sat, 05 Sep 2026 06:10:57 +0000</pubDate>
      <link>https://dev.to/wbizmo/im-almost-done-building-the-biggest-financial-platform-ive-worked-on-47ko</link>
      <guid>https://dev.to/wbizmo/im-almost-done-building-the-biggest-financial-platform-ive-worked-on-47ko</guid>
      <description>&lt;p&gt;I kept telling myself I would write about this project when I finished it.&lt;/p&gt;

&lt;p&gt;I'm still not completely finished.&lt;/p&gt;

&lt;p&gt;There are a few user journeys I'm checking again, some UI things I keep noticing at annoying screen sizes, a performance problem I deliberately don't want to "fix" before measuring it properly, and a few pieces of technical debt I already know I'll be coming back for.&lt;/p&gt;

&lt;p&gt;But I'm close.&lt;/p&gt;

&lt;p&gt;And I think I'd rather write this now, while I'm still inside the build, than six months from now when my brain has conveniently turned the whole experience into a clean sequence of sensible engineering decisions.&lt;/p&gt;

&lt;p&gt;Because it wasn't that clean.&lt;/p&gt;

&lt;p&gt;I've been shipping almost every day on a financial platform through a pretty intense stretch of development.&lt;/p&gt;

&lt;p&gt;I can't name the company, the product, or the financial infrastructure behind it, and there are obviously security and production details that don't belong in a public article.&lt;/p&gt;

&lt;p&gt;But I can talk about what it has actually been like to build.&lt;/p&gt;

&lt;p&gt;At one point, a repository snapshot looked roughly like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;700+ commits&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1,000+ tracked files&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;200,000+ tracked lines&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 applications&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;13 shared packages&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;500+ tests&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don't think I ever had a day where it felt like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Today I am contributing to a 200,000-line system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It felt more like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why is this route sending an authenticated user back to login?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This notification should open the thing it is notifying them about.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why can this worker be delayed by marketing jobs?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This transfer timed out. Did the money actually move?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This looks terrible on a 390px screen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Enough of those days happen and suddenly you have a platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the project stopped being "an app"
&lt;/h2&gt;

&lt;p&gt;The repository eventually settled into a TypeScript monorepo with three main applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/
  api/
  web/
  worker/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and shared packages handling things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages/
  cache/
  config/
  database/
  documents/
  ledger/
  providers/
  public-api/
  queues/
  safety/
  security/
  shared/
  storage/
  tooling/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend is React and TypeScript.&lt;/p&gt;

&lt;p&gt;The HTTP layer is Fastify.&lt;/p&gt;

&lt;p&gt;PostgreSQL is the primary database.&lt;/p&gt;

&lt;p&gt;PgBouncer handles connection pooling.&lt;/p&gt;

&lt;p&gt;Valkey sits behind caching and short-lived coordination.&lt;/p&gt;

&lt;p&gt;BullMQ runs asynchronous workloads.&lt;/p&gt;

&lt;p&gt;Around that are object storage, Caddy, systemd, structured logging, health checks, deployment tooling, API schemas, background workers, and the other things that become necessary once "run the server" stops being a deployment strategy.&lt;/p&gt;

&lt;p&gt;None of those technology choices are particularly exotic.&lt;/p&gt;

&lt;p&gt;The interesting part was what the product gradually demanded from them.&lt;/p&gt;

&lt;p&gt;What started as one financial application eventually included customer onboarding, account approval, KYC, financial accounts, transfers, FX, cards, bill payments, statements, notifications, partner workflows, internal operations, support tooling, developer-facing APIs, public documentation, a PWA, and some separately privileged product areas.&lt;/p&gt;

&lt;p&gt;Different users.&lt;/p&gt;

&lt;p&gt;Different access boundaries.&lt;/p&gt;

&lt;p&gt;Different kinds of state.&lt;/p&gt;

&lt;p&gt;Different kinds of money movement.&lt;/p&gt;

&lt;p&gt;Different failure modes.&lt;/p&gt;

&lt;p&gt;One system.&lt;/p&gt;

&lt;p&gt;At the beginning, I was still mostly thinking in features.&lt;/p&gt;

&lt;p&gt;Build onboarding.&lt;/p&gt;

&lt;p&gt;Build the dashboard.&lt;/p&gt;

&lt;p&gt;Add transfers.&lt;/p&gt;

&lt;p&gt;Add notifications.&lt;/p&gt;

&lt;p&gt;Wire up the provider.&lt;/p&gt;

&lt;p&gt;Make the mobile version work.&lt;/p&gt;

&lt;p&gt;The problem is that every one of those features brought more than a screen with it.&lt;/p&gt;

&lt;p&gt;Transfers brought retries, timeouts, provider state, reconciliation, authorization, limits, notifications, and audit.&lt;/p&gt;

&lt;p&gt;Notifications brought preferences, retries, delivery channels, deep links, and several applications generating different kinds of events.&lt;/p&gt;

&lt;p&gt;Partner features brought attribution, permissions, workflow state, and the problem of showing enough information to make a relationship useful without leaking private customer data.&lt;/p&gt;

&lt;p&gt;Eventually the implementation had grown enough that I stopped trusting the shape of the original application.&lt;/p&gt;

&lt;p&gt;Not because every part was bad.&lt;/p&gt;

&lt;p&gt;That actually made the decision harder.&lt;/p&gt;

&lt;p&gt;There is a point in a project where almost every problem still looks individually fixable.&lt;/p&gt;

&lt;p&gt;A route is broken.&lt;/p&gt;

&lt;p&gt;Fix it.&lt;/p&gt;

&lt;p&gt;An authorization check is awkward.&lt;/p&gt;

&lt;p&gt;Fix it.&lt;/p&gt;

&lt;p&gt;One provider call is living in the wrong place.&lt;/p&gt;

&lt;p&gt;Move it.&lt;/p&gt;

&lt;p&gt;A page breaks on mobile.&lt;/p&gt;

&lt;p&gt;Patch the CSS.&lt;/p&gt;

&lt;p&gt;A workflow has a weird state.&lt;/p&gt;

&lt;p&gt;Add another condition.&lt;/p&gt;

&lt;p&gt;Every individual fix can be reasonable while the overall shape of the application gets worse.&lt;/p&gt;

&lt;p&gt;Eventually I reached the point where I didn't trust enough of the assumptions underneath the implementation.&lt;/p&gt;

&lt;p&gt;So I froze that development state.&lt;/p&gt;

&lt;p&gt;I kept the infrastructure, but started the application layer again on a clean development environment.&lt;/p&gt;

&lt;p&gt;Not because rewrites are fun.&lt;/p&gt;

&lt;p&gt;They're not.&lt;/p&gt;

&lt;p&gt;It was more:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't want to keep making stronger promises on top of a foundation I no longer fully trust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The original implementation plan had 24 engineering sprints.&lt;/p&gt;

&lt;p&gt;That eventually became 30.&lt;/p&gt;

&lt;p&gt;Not because I suddenly wanted six more sprints, but because areas like partner operations, developer APIs, documentation, PWA behaviour, and final hardening turned out to be much larger domains when I tried to make them &lt;strong&gt;complete&lt;/strong&gt; instead of merely present.&lt;/p&gt;

&lt;p&gt;That gave me one of the first lessons from the project that really stuck:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sometimes scope grows because people keep adding things. Sometimes scope grows because you finally understand what the original sentence actually implied.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those are very different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebuilding the foundation changed what I meant by "reliable"
&lt;/h2&gt;

&lt;p&gt;One of the first clean-build deployments failed.&lt;/p&gt;

&lt;p&gt;I like that story now.&lt;/p&gt;

&lt;p&gt;I did not like it when it happened.&lt;/p&gt;

&lt;p&gt;The useful part wasn't that the deployment failed.&lt;/p&gt;

&lt;p&gt;The useful part was that the release machinery did what it was supposed to do.&lt;/p&gt;

&lt;p&gt;The new state wasn't copied over the running application and left there half-working. The release stopped and the previous known-good state came back.&lt;/p&gt;

&lt;p&gt;The real deployment tooling is more involved, but conceptually the release flow became something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;release&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;create_release&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

build_into &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$release&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
run_checks &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$release&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

switch_atomically &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$release&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; healthcheck&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;rollback
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had already written "rollback" into the architecture.&lt;/p&gt;

&lt;p&gt;That failure was the moment I knew rollback actually existed.&lt;/p&gt;

&lt;p&gt;There is a difference.&lt;/p&gt;

&lt;p&gt;I also learned a smaller operational detail by watching enough deployments: the first health probe after switching a release doesn't always return &lt;code&gt;200&lt;/code&gt; immediately.&lt;/p&gt;

&lt;p&gt;Sometimes the process needs another attempt or two while it finishes warming up.&lt;/p&gt;

&lt;p&gt;The first few times that happened, I treated it like something was broken.&lt;/p&gt;

&lt;p&gt;Eventually the deployment tooling learned what normal startup behaviour actually looked like.&lt;/p&gt;

&lt;p&gt;That's one of those boring things you don't really learn from drawing an architecture diagram.&lt;/p&gt;

&lt;p&gt;Architecture tells you how you think the system behaves.&lt;/p&gt;

&lt;p&gt;Operations tells you how it actually behaves.&lt;/p&gt;

&lt;p&gt;That experience also made me stricter about apparently small changes.&lt;/p&gt;

&lt;p&gt;At one point CI went red because Prettier wanted a multi-line import collapsed back onto one line.&lt;/p&gt;

&lt;p&gt;That was basically the entire behavioural impact of the commit.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;AppSessionLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PushPermissionDecision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;RequestCard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Shell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./MemberPortal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppSessionLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PushPermissionDecision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RequestCard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Shell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./MemberPortal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No business behaviour changed.&lt;/p&gt;

&lt;p&gt;The gate was still red.&lt;/p&gt;

&lt;p&gt;So the formatting change got synced, built, deployed, and verified like everything else.&lt;/p&gt;

&lt;p&gt;At the time I thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is ridiculous.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then I thought about the alternative.&lt;/p&gt;

&lt;p&gt;You start deciding that some changes are too small to deserve the normal release process.&lt;/p&gt;

&lt;p&gt;Then somebody has to decide what "small" means.&lt;/p&gt;

&lt;p&gt;Then exceptions start accumulating.&lt;/p&gt;

&lt;p&gt;So yes.&lt;/p&gt;

&lt;p&gt;The one-line import got a proper deployment.&lt;/p&gt;

&lt;p&gt;The Git setup behind all of this is less elegant than I'd like.&lt;/p&gt;

&lt;p&gt;I deliberately separated the environment where commits are authored from the checkout that handles deployment and remote repository authentication.&lt;/p&gt;

&lt;p&gt;That buys credential isolation.&lt;/p&gt;

&lt;p&gt;It also means synchronization occasionally takes more thought than a normal one-checkout workflow should. I've had moments where the deployment checkout already contained an uncommitted copy of the exact change I was trying to pull as a commit, forcing me to verify the diffs matched before pulling cleanly.&lt;/p&gt;

&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;I know why it exists.&lt;/p&gt;

&lt;p&gt;I still want to simplify it.&lt;/p&gt;

&lt;p&gt;That's debt. Not hidden debt. Just something useful enough to tolerate for now.&lt;/p&gt;

&lt;p&gt;The bigger shift was that I started thinking about reliability less as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this code work?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and more as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What does the system do when this code doesn't get the outcome it expected?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question became much more serious once money movement entered the picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Money movement forced me to stop thinking in success and failure
&lt;/h2&gt;

&lt;p&gt;From the UI, a transfer looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;recipient
amount
confirm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend version is much less pleasant.&lt;/p&gt;

&lt;p&gt;Suppose I submit a transfer to an external financial provider.&lt;/p&gt;

&lt;p&gt;Then my connection disappears.&lt;/p&gt;

&lt;p&gt;Did it fail?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;Did the provider receive the request?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;Did they execute it successfully and the response disappear somewhere between their system and mine?&lt;/p&gt;

&lt;p&gt;Also maybe.&lt;/p&gt;

&lt;p&gt;The one thing I definitely cannot do is immediately submit the transfer again.&lt;/p&gt;

&lt;p&gt;A naïve version of the flow wants to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Transfer failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That code wants the world to have two states.&lt;/p&gt;

&lt;p&gt;Success.&lt;/p&gt;

&lt;p&gt;Failure.&lt;/p&gt;

&lt;p&gt;Financial systems occasionally respond:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;lol, no.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The application eventually needed stable operation IDs, idempotency keys, request hashes, provider references, and recoverable operation states.&lt;/p&gt;

&lt;p&gt;The mental model became much closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OperationState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accepted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reversed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reconciling&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The state I care about most there is &lt;code&gt;unknown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Earlier in my career, I think I would have treated &lt;code&gt;unknown&lt;/code&gt; as a failure to finish implementing the workflow.&lt;/p&gt;

&lt;p&gt;Now I think it can be one of the most honest states in a distributed financial system.&lt;/p&gt;

&lt;p&gt;If I genuinely do not know whether money moved yet, the application should not invent an answer because red or green is easier to render.&lt;/p&gt;

&lt;p&gt;That same problem changed how I thought about webhooks.&lt;/p&gt;

&lt;p&gt;A webhook implementation initially looks very simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you spend five minutes thinking like someone trying to break it.&lt;/p&gt;

&lt;p&gt;Who sent that body?&lt;/p&gt;

&lt;p&gt;Has it been modified?&lt;/p&gt;

&lt;p&gt;How old is it?&lt;/p&gt;

&lt;p&gt;Have I seen this event before?&lt;/p&gt;

&lt;p&gt;Is it from the right environment?&lt;/p&gt;

&lt;p&gt;What if the provider sends it three times?&lt;/p&gt;

&lt;p&gt;What if they retry because my server took too long to answer?&lt;/p&gt;

&lt;p&gt;What if the webhook says a transaction completed but the amount or reference does not match what I expected?&lt;/p&gt;

&lt;p&gt;Eventually the flow looked much more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receive
  ↓
validate request shape
  ↓
verify signature + timestamp
  ↓
reject replay
  ↓
persist event
  ↓
acknowledge quickly
  ↓
process asynchronously
  ↓
verify against provider when necessary
  ↓
apply financial effect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And reconciliation stopped pretending that every incoming event could immediately become success or failure.&lt;/p&gt;

&lt;p&gt;A shortened, anonymised version of a pattern in the codebase looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReconcileOutcome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;postingId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;duplicate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skipped&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;needs_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;needs_review&lt;/code&gt; state matters.&lt;/p&gt;

&lt;p&gt;For example, if I receive one reference and the authoritative lookup comes back with something else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;verifiedReference&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;needs_review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Provider verification reference mismatch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't guess.&lt;/p&gt;

&lt;p&gt;Don't force it through because it probably belongs to that transaction.&lt;/p&gt;

&lt;p&gt;Don't silently throw it away either.&lt;/p&gt;

&lt;p&gt;Put it somewhere the system can reason about it safely.&lt;/p&gt;

&lt;p&gt;The more I worked on this, the more I realised that a financial platform needs to be comfortable saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I have received information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;without automatically saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Therefore this is now financial truth.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are different statements.&lt;/p&gt;

&lt;p&gt;And that distinction led directly to another architectural problem.&lt;/p&gt;

&lt;p&gt;The more financial operations I added, the easier it would have been to let the external provider define the whole application.&lt;/p&gt;

&lt;p&gt;Need an account?&lt;/p&gt;

&lt;p&gt;Call their account endpoint.&lt;/p&gt;

&lt;p&gt;Need a transfer?&lt;/p&gt;

&lt;p&gt;Call their transfer endpoint.&lt;/p&gt;

&lt;p&gt;Need FX?&lt;/p&gt;

&lt;p&gt;Call that endpoint.&lt;/p&gt;

&lt;p&gt;Need a card?&lt;/p&gt;

&lt;p&gt;Same thing.&lt;/p&gt;

&lt;p&gt;That is extremely productive at the beginning.&lt;/p&gt;

&lt;p&gt;Then one day your controllers know provider-specific field names, your workers know provider-specific status strings, your frontend knows which provider can do what, and half your business logic is written in the vocabulary of a company you don't control.&lt;/p&gt;

&lt;p&gt;I didn't want that.&lt;/p&gt;

&lt;p&gt;So financial integrations ended up behind a provider boundary.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application/domain logic
          ↓
provider capability + routing layer
          ↓
provider-specific adapter
          ↓
external infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An early implementation could easily have ended up with logic like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showTransferButton&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Except "does the provider support USD?" is not actually a useful question.&lt;/p&gt;

&lt;p&gt;USD for what?&lt;/p&gt;

&lt;p&gt;Account issuance?&lt;/p&gt;

&lt;p&gt;Holding a balance?&lt;/p&gt;

&lt;p&gt;Inbound transfer?&lt;/p&gt;

&lt;p&gt;Outbound transfer?&lt;/p&gt;

&lt;p&gt;FX?&lt;/p&gt;

&lt;p&gt;Cards?&lt;/p&gt;

&lt;p&gt;Bills?&lt;/p&gt;

&lt;p&gt;A provider can support one and not another.&lt;/p&gt;

&lt;p&gt;Or expose something technically while that capability is not enabled for your account.&lt;/p&gt;

&lt;p&gt;So support had to become contextual.&lt;/p&gt;

&lt;p&gt;The provider layer can ask something closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;supports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;banking_rail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;customerType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with the underlying logic checking capability, geography, currency, customer type, and whether that declaration is enabled.&lt;/p&gt;

&lt;p&gt;Something roughly like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;capability&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countries&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currencies&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;declaration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is another detail I particularly like.&lt;/p&gt;

&lt;p&gt;Once an existing financial relationship has been created with a particular provider, changing the routing preference for new relationships should not silently move that existing one somewhere else.&lt;/p&gt;

&lt;p&gt;So existing relationships can be pinned.&lt;/p&gt;

&lt;p&gt;New ones can follow the current routing rules.&lt;/p&gt;

&lt;p&gt;Old ones continue using the provider they were actually created with.&lt;/p&gt;

&lt;p&gt;That sounds obvious now.&lt;/p&gt;

&lt;p&gt;It wasn't something I was thinking about before I had to design a system that could survive changing providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Once correctness mattered, infrastructure became part of product behaviour
&lt;/h2&gt;

&lt;p&gt;The financial workflows created more asynchronous work.&lt;/p&gt;

&lt;p&gt;Provider events.&lt;/p&gt;

&lt;p&gt;Reconciliation.&lt;/p&gt;

&lt;p&gt;Security messages.&lt;/p&gt;

&lt;p&gt;Notifications.&lt;/p&gt;

&lt;p&gt;Documents.&lt;/p&gt;

&lt;p&gt;Exports.&lt;/p&gt;

&lt;p&gt;Analytics.&lt;/p&gt;

&lt;p&gt;At first, the normal queue question was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this need to happen outside the HTTP request?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Useful question.&lt;/p&gt;

&lt;p&gt;Not enough.&lt;/p&gt;

&lt;p&gt;The more important question became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is this job allowed to wait behind?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine thousands of marketing jobs enter the queue.&lt;/p&gt;

&lt;p&gt;At the same time, somebody is trying to log in and needs a security email.&lt;/p&gt;

&lt;p&gt;Should that security message sit behind the marketing campaign?&lt;/p&gt;

&lt;p&gt;Obviously not.&lt;/p&gt;

&lt;p&gt;But if they are all just "background jobs," you may have accidentally made exactly that decision.&lt;/p&gt;

&lt;p&gt;So the queue layer stopped treating every job as equal.&lt;/p&gt;

&lt;p&gt;A simplified version looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queueClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;security-email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;provider-webhooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;financial-reconciliation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="na"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transactional-email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;push-notifications&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="na"&gt;deferrable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exports&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analytics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;marketing-bulk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;maintenance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They don't all get the same retry behaviour either:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;policies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;deferrable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specific numbers are not the interesting part.&lt;/p&gt;

&lt;p&gt;The interesting part is that queue design became a product decision.&lt;/p&gt;

&lt;p&gt;When the system is under pressure, &lt;strong&gt;what are we willing to make wait?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An analytics refresh?&lt;/p&gt;

&lt;p&gt;Sure.&lt;/p&gt;

&lt;p&gt;A login security message?&lt;/p&gt;

&lt;p&gt;Much less so.&lt;/p&gt;

&lt;p&gt;A financial reconciliation job?&lt;/p&gt;

&lt;p&gt;Definitely not because somebody requested a large export.&lt;/p&gt;

&lt;p&gt;That's how I think about queues now.&lt;/p&gt;

&lt;p&gt;They aren't just a place to put work you don't want inside the request.&lt;/p&gt;

&lt;p&gt;They encode priority.&lt;/p&gt;

&lt;p&gt;They encode which delays the product considers acceptable.&lt;/p&gt;

&lt;p&gt;The database went through a similar change in my head.&lt;/p&gt;

&lt;p&gt;PostgreSQL stopped being just "where the data goes."&lt;/p&gt;

&lt;p&gt;There are some bugs I don't want application-level &lt;code&gt;if&lt;/code&gt; statements to be solely responsible for preventing.&lt;/p&gt;

&lt;p&gt;Two requests trying to spend the same available amount.&lt;/p&gt;

&lt;p&gt;Two workers processing the same event.&lt;/p&gt;

&lt;p&gt;Two people trying to consume the last available unit of something.&lt;/p&gt;

&lt;p&gt;An update based on stale state.&lt;/p&gt;

&lt;p&gt;Several records that need to change together or not change at all.&lt;/p&gt;

&lt;p&gt;So the database became part of the correctness model.&lt;/p&gt;

&lt;p&gt;That meant actually using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unique constraints;&lt;/li&gt;
&lt;li&gt;foreign keys;&lt;/li&gt;
&lt;li&gt;check constraints;&lt;/li&gt;
&lt;li&gt;optimistic version columns;&lt;/li&gt;
&lt;li&gt;atomic conditional updates;&lt;/li&gt;
&lt;li&gt;row locks;&lt;/li&gt;
&lt;li&gt;short transactions;&lt;/li&gt;
&lt;li&gt;deterministic lock ordering;&lt;/li&gt;
&lt;li&gt;and stronger isolation where a specific invariant justified it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I knew what &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; did before this project.&lt;/p&gt;

&lt;p&gt;The project taught me something more useful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;when I actually want it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same with serializable transactions.&lt;/p&gt;

&lt;p&gt;I knew they existed.&lt;/p&gt;

&lt;p&gt;Now I understand much better why I don't want everything running at serializable isolation, and why a few particularly sensitive invariants might actually justify paying that cost.&lt;/p&gt;

&lt;p&gt;Knowing the feature and knowing where it belongs are different levels of understanding.&lt;/p&gt;

&lt;p&gt;That pattern kept repeating.&lt;/p&gt;

&lt;p&gt;The system would grow.&lt;/p&gt;

&lt;p&gt;A problem I previously understood academically would become a real operational constraint.&lt;/p&gt;

&lt;p&gt;Then I would understand why the boring engineering primitive exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The visible feature was almost never the whole feature
&lt;/h2&gt;

&lt;p&gt;Authentication started small too.&lt;/p&gt;

&lt;p&gt;At one point, "authentication" basically meant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;login
logout
forgot password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the actual requirements arrived.&lt;/p&gt;

&lt;p&gt;OTP.&lt;/p&gt;

&lt;p&gt;Passkeys.&lt;/p&gt;

&lt;p&gt;Device and session management.&lt;/p&gt;

&lt;p&gt;Session rotation.&lt;/p&gt;

&lt;p&gt;Step-up authentication.&lt;/p&gt;

&lt;p&gt;Recovery.&lt;/p&gt;

&lt;p&gt;Staff authentication.&lt;/p&gt;

&lt;p&gt;Privileged staff authentication.&lt;/p&gt;

&lt;p&gt;Trusted devices.&lt;/p&gt;

&lt;p&gt;Revocation.&lt;/p&gt;

&lt;p&gt;Different session policies.&lt;/p&gt;

&lt;p&gt;Authorization became even more important because the system eventually had several application surfaces.&lt;/p&gt;

&lt;p&gt;A customer.&lt;/p&gt;

&lt;p&gt;A partner.&lt;/p&gt;

&lt;p&gt;A developer.&lt;/p&gt;

&lt;p&gt;Normal staff.&lt;/p&gt;

&lt;p&gt;Privileged staff.&lt;/p&gt;

&lt;p&gt;Someone can be allowed to see a record without being allowed to modify it.&lt;/p&gt;

&lt;p&gt;A partner may need to know that a referral progressed without being allowed anywhere near that person's banking details.&lt;/p&gt;

&lt;p&gt;A customer interface may correctly hide an operation while an API route underneath it accidentally still allows the request.&lt;/p&gt;

&lt;p&gt;So I became increasingly annoying about one rule:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A hidden or disabled button is not authorization.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If someone is not allowed to perform an action, the server performing that action has to reject it.&lt;/p&gt;

&lt;p&gt;Not the menu.&lt;/p&gt;

&lt;p&gt;Not the React component.&lt;/p&gt;

&lt;p&gt;Not the disabled state.&lt;/p&gt;

&lt;p&gt;The actual boundary that changes or reveals the protected resource.&lt;/p&gt;

&lt;p&gt;Frontend visibility is UX.&lt;/p&gt;

&lt;p&gt;Authorization is policy.&lt;/p&gt;

&lt;p&gt;Notifications followed the same pattern.&lt;/p&gt;

&lt;p&gt;What began as "we need notifications" became in-app events, email, push, preferences, retries, deduplication, deep links, and failure handling across several application surfaces.&lt;/p&gt;

&lt;p&gt;The useful model was to treat the internal notification as the durable record and email or push as delivery attempts around it.&lt;/p&gt;

&lt;p&gt;A failed delivery does not undo the event that caused it, and it definitely does not change financial truth.&lt;/p&gt;

&lt;p&gt;And while all of this was happening on the backend, the frontend was teaching me a different version of the same lesson:&lt;/p&gt;

&lt;p&gt;software can be technically correct and still be bad.&lt;/p&gt;

&lt;p&gt;The mobile topbar, for example, had several lives.&lt;/p&gt;

&lt;p&gt;It went from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;full wordmark
      ↓
personalised greeting
      ↓
small icon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The greeting version wasn't a mock-up.&lt;/p&gt;

&lt;p&gt;It actually loaded the member's name.&lt;/p&gt;

&lt;p&gt;It had responsive typography.&lt;/p&gt;

&lt;p&gt;It was built and styled.&lt;/p&gt;

&lt;p&gt;Then I looked at it again and the smaller icon was cleaner.&lt;/p&gt;

&lt;p&gt;So the greeting disappeared.&lt;/p&gt;

&lt;p&gt;I used to think of that kind of work as wasted effort.&lt;/p&gt;

&lt;p&gt;I don't anymore.&lt;/p&gt;

&lt;p&gt;Sometimes the fastest way to know whether a visual idea works is to build the thing and look at it.&lt;/p&gt;

&lt;p&gt;One of my favourite frontend bugs had nothing to do with code correctness.&lt;/p&gt;

&lt;p&gt;A dashboard section was redesigned.&lt;/p&gt;

&lt;p&gt;The code compiled.&lt;/p&gt;

&lt;p&gt;Types passed.&lt;/p&gt;

&lt;p&gt;Routes worked.&lt;/p&gt;

&lt;p&gt;The layout worked.&lt;/p&gt;

&lt;p&gt;Then I looked at the screen and realised that the same phrase appeared three times in the same panel.&lt;/p&gt;

&lt;p&gt;Once as the eyebrow.&lt;/p&gt;

&lt;p&gt;Once as the heading.&lt;/p&gt;

&lt;p&gt;Once again on the button.&lt;/p&gt;

&lt;p&gt;Perfectly valid software.&lt;/p&gt;

&lt;p&gt;Terrible interface.&lt;/p&gt;

&lt;p&gt;No type system is going to catch that.&lt;/p&gt;

&lt;p&gt;No unit test is going to tell you your copy hierarchy looks stupid.&lt;/p&gt;

&lt;p&gt;Sometimes the correct QA process is literally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why does this look stupid?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mobile made this even more obvious.&lt;/p&gt;

&lt;p&gt;There were pages that were technically responsive.&lt;/p&gt;

&lt;p&gt;Nothing overflowed.&lt;/p&gt;

&lt;p&gt;Every element was visible.&lt;/p&gt;

&lt;p&gt;They were still bad mobile interfaces.&lt;/p&gt;

&lt;p&gt;Authentication was the clearest example.&lt;/p&gt;

&lt;p&gt;On desktop, more explanatory text and a wider composition can look good.&lt;/p&gt;

&lt;p&gt;On a phone, especially inside an installed PWA, the same thing can feel like a desktop website somebody squeezed into a smaller rectangle.&lt;/p&gt;

&lt;p&gt;So I started removing things.&lt;/p&gt;

&lt;p&gt;Less surrounding chrome.&lt;/p&gt;

&lt;p&gt;Less unnecessary copy.&lt;/p&gt;

&lt;p&gt;Better viewport spacing.&lt;/p&gt;

&lt;p&gt;Forms that actually sit comfortably between the screen edges.&lt;/p&gt;

&lt;p&gt;Cleaner hierarchy.&lt;/p&gt;

&lt;p&gt;OTP fields that look like OTP fields instead of a normal text input.&lt;/p&gt;

&lt;p&gt;Navigation designed for thumbs instead of a desktop sidebar awkwardly collapsing.&lt;/p&gt;

&lt;p&gt;That changed the question I ask.&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this fit on mobile?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If mobile were the only platform, would I have designed it this way?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is no, then I probably haven't actually designed the mobile version yet.&lt;/p&gt;

&lt;p&gt;The same idea connects all of these problems.&lt;/p&gt;

&lt;p&gt;A disabled button doesn't mean an action is forbidden.&lt;/p&gt;

&lt;p&gt;A page fitting inside the viewport doesn't mean it is a good mobile experience.&lt;/p&gt;

&lt;p&gt;A transfer button working doesn't mean the transfer journey is reliable.&lt;/p&gt;

&lt;p&gt;The thing I could see was often only the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Done" became a much harsher word
&lt;/h2&gt;

&lt;p&gt;Near the end of the build, I started discovering a lot of things that existed but weren't actually complete.&lt;/p&gt;

&lt;p&gt;A notification bell could exist without every product surface producing the right events.&lt;/p&gt;

&lt;p&gt;A developer portal could look finished while credential rotation, scopes, idempotency, or webhook management were still incomplete.&lt;/p&gt;

&lt;p&gt;An admin page could render perfectly while the underlying action lacked the right authorization, audit trail, or failure behaviour.&lt;/p&gt;

&lt;p&gt;That was when my working definition of a complete feature became much harsher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data model
backend action
authorization
validation
state transitions
audit
notifications
failure states
empty states
responsive behaviour
tests
documentation
operational controls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That definition is painful when somebody says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But the screen is already there. Aren't we almost done?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes.&lt;/p&gt;

&lt;p&gt;Sometimes the screen means you're 60% done.&lt;/p&gt;

&lt;p&gt;Testing changed for me too.&lt;/p&gt;

&lt;p&gt;The project has more than 500 tests, and substantial behavioural changes still get the appropriate suites.&lt;/p&gt;

&lt;p&gt;But I stopped treating "run everything after everything" as synonymous with discipline.&lt;/p&gt;

&lt;p&gt;For a presentation-only change, typecheck, build, and actual visual inspection may tell me more than hundreds of unrelated domain tests.&lt;/p&gt;

&lt;p&gt;My rule became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pay for the verification that can realistically catch the class of mistake you just introduced.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I changed a ledger invariant, visual QA isn't going to save me.&lt;/p&gt;

&lt;p&gt;If I changed a heading from &lt;code&gt;18px&lt;/code&gt; to &lt;code&gt;20px&lt;/code&gt;, hundreds of domain tests probably aren't going to save me either.&lt;/p&gt;

&lt;p&gt;And despite all of this, the project isn't magically debt-free because I'm near the end.&lt;/p&gt;

&lt;p&gt;The main frontend bundle is still larger than I want it to be.&lt;/p&gt;

&lt;p&gt;The bundler complains about it.&lt;/p&gt;

&lt;p&gt;Correctly.&lt;/p&gt;

&lt;p&gt;I have deliberately not responded by throwing &lt;code&gt;lazy()&lt;/code&gt; around random components and declaring the performance problem solved.&lt;/p&gt;

&lt;p&gt;There is a proper performance investigation queued.&lt;/p&gt;

&lt;p&gt;The bundle is an obvious place to start, but I want to measure the actual user-visible latency before deciding what needs to change.&lt;/p&gt;

&lt;p&gt;There is also an authenticated dashboard component that has accumulated too many responsibilities, an icon fallback that is resilient but can hide misspelled glyph names, and some operational cleanup I deliberately postponed until the feature pressure drops.&lt;/p&gt;

&lt;p&gt;I have debt.&lt;/p&gt;

&lt;p&gt;I know where it is.&lt;/p&gt;

&lt;p&gt;I know why it exists.&lt;/p&gt;

&lt;p&gt;I've become much more comfortable with the difference between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;unknown, accidental debt&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;known, bounded debt that I deliberately chose not to fix today.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second kind still needs paying.&lt;/p&gt;

&lt;p&gt;But at least I know what I owe.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that changed how I engineer
&lt;/h2&gt;

&lt;p&gt;I've tried to work out which part of the project had the biggest impact on me.&lt;/p&gt;

&lt;p&gt;The transfer system?&lt;/p&gt;

&lt;p&gt;The provider abstraction?&lt;/p&gt;

&lt;p&gt;The ledger?&lt;/p&gt;

&lt;p&gt;Security?&lt;/p&gt;

&lt;p&gt;Partner workflows?&lt;/p&gt;

&lt;p&gt;Developer APIs?&lt;/p&gt;

&lt;p&gt;Deployment?&lt;/p&gt;

&lt;p&gt;The PWA?&lt;/p&gt;

&lt;p&gt;The queue architecture?&lt;/p&gt;

&lt;p&gt;I don't think it is one feature.&lt;/p&gt;

&lt;p&gt;The bigger job was repeatedly taking vague product statements and turning them into rules a system could actually enforce.&lt;/p&gt;

&lt;p&gt;Take:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Users should be able to transfer money.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Okay.&lt;/p&gt;

&lt;p&gt;Which users?&lt;/p&gt;

&lt;p&gt;From which accounts?&lt;/p&gt;

&lt;p&gt;At what account state?&lt;/p&gt;

&lt;p&gt;Which currencies?&lt;/p&gt;

&lt;p&gt;Which rails?&lt;/p&gt;

&lt;p&gt;Which provider capabilities?&lt;/p&gt;

&lt;p&gt;Which verification state?&lt;/p&gt;

&lt;p&gt;What limits?&lt;/p&gt;

&lt;p&gt;How is the recipient resolved?&lt;/p&gt;

&lt;p&gt;How long is that resolution trusted?&lt;/p&gt;

&lt;p&gt;Can somebody transfer to themselves?&lt;/p&gt;

&lt;p&gt;What happens if they submit twice?&lt;/p&gt;

&lt;p&gt;What happens if the connection disappears?&lt;/p&gt;

&lt;p&gt;What if the provider actually processed the transfer?&lt;/p&gt;

&lt;p&gt;What if the webhook arrives first?&lt;/p&gt;

&lt;p&gt;What if it arrives twice?&lt;/p&gt;

&lt;p&gt;What does the user see while we genuinely don't know?&lt;/p&gt;

&lt;p&gt;What does support see?&lt;/p&gt;

&lt;p&gt;What gets logged?&lt;/p&gt;

&lt;p&gt;What gets audited?&lt;/p&gt;

&lt;p&gt;Who gets notified?&lt;/p&gt;

&lt;p&gt;What happens if a dependency is unavailable?&lt;/p&gt;

&lt;p&gt;Then repeat that exercise for cards.&lt;/p&gt;

&lt;p&gt;FX.&lt;/p&gt;

&lt;p&gt;KYC.&lt;/p&gt;

&lt;p&gt;Bill payments.&lt;/p&gt;

&lt;p&gt;Notifications.&lt;/p&gt;

&lt;p&gt;Partner referrals.&lt;/p&gt;

&lt;p&gt;Developer applications.&lt;/p&gt;

&lt;p&gt;Documents.&lt;/p&gt;

&lt;p&gt;Authentication.&lt;/p&gt;

&lt;p&gt;Administration.&lt;/p&gt;

&lt;p&gt;At some point I realised that a huge part of my work had become finding every unanswered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Okay, but what happens if...?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and making the system choose an answer deliberately.&lt;/p&gt;

&lt;p&gt;That's probably the best description I have for the project.&lt;/p&gt;

&lt;p&gt;Earlier in the build, I was much more likely to fix the visible problem.&lt;/p&gt;

&lt;p&gt;Authenticated user gets kicked back to login?&lt;/p&gt;

&lt;p&gt;Fix the redirect.&lt;/p&gt;

&lt;p&gt;Button doesn't work?&lt;/p&gt;

&lt;p&gt;Fix the button.&lt;/p&gt;

&lt;p&gt;Mobile page looks broken?&lt;/p&gt;

&lt;p&gt;Fix that page.&lt;/p&gt;

&lt;p&gt;Later I became more annoying.&lt;/p&gt;

&lt;p&gt;Why did an authorization failure become an authentication failure?&lt;/p&gt;

&lt;p&gt;Does another route use the same middleware pattern?&lt;/p&gt;

&lt;p&gt;Why was the button able to request an operation the server should have rejected anyway?&lt;/p&gt;

&lt;p&gt;Is the responsive bug local to this component, or is the same broken primitive used on twelve other pages?&lt;/p&gt;

&lt;p&gt;Somewhere in that process, the way I debug changed.&lt;/p&gt;

&lt;p&gt;I moved from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;fix the bug&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;toward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;fix the invariant that allowed this class of bug.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second one can take longer.&lt;/p&gt;

&lt;p&gt;It tends to pay back.&lt;/p&gt;

&lt;p&gt;There are definitely things I would do differently if I were starting again.&lt;/p&gt;

&lt;p&gt;I would define important state machines much earlier.&lt;/p&gt;

&lt;p&gt;Before the UI.&lt;/p&gt;

&lt;p&gt;Before controllers.&lt;/p&gt;

&lt;p&gt;Before three different parts of the application all have their own interpretation of what &lt;code&gt;pending&lt;/code&gt; means.&lt;/p&gt;

&lt;p&gt;I would establish provider boundaries earlier.&lt;/p&gt;

&lt;p&gt;It is much easier to prevent provider-specific knowledge from spreading than to remove it later.&lt;/p&gt;

&lt;p&gt;I would design mobile as its own interaction problem from day one.&lt;/p&gt;

&lt;p&gt;I would establish stronger conventions around notifications, authorization, and operational states before several domains needed them.&lt;/p&gt;

&lt;p&gt;I would define "done" much more harshly at the beginning.&lt;/p&gt;

&lt;p&gt;And yes, I would simplify the Git setup.&lt;/p&gt;

&lt;p&gt;Very much that last one.&lt;/p&gt;

&lt;p&gt;I like the stack I'm using.&lt;/p&gt;

&lt;p&gt;But I don't think "I learned Fastify" or "I used BullMQ" is what I'll remember from this project.&lt;/p&gt;

&lt;p&gt;What I'll remember is how often I ended up asking:&lt;/p&gt;

&lt;p&gt;Where does truth live?&lt;/p&gt;

&lt;p&gt;Who owns this state?&lt;/p&gt;

&lt;p&gt;Who is allowed to change it?&lt;/p&gt;

&lt;p&gt;What happens if this runs twice?&lt;/p&gt;

&lt;p&gt;What happens if it succeeds externally but fails internally?&lt;/p&gt;

&lt;p&gt;Can I safely retry it?&lt;/p&gt;

&lt;p&gt;If I can't retry it, how do I recover?&lt;/p&gt;

&lt;p&gt;Does the database need to enforce this?&lt;/p&gt;

&lt;p&gt;Should this piece of work be allowed to wait?&lt;/p&gt;

&lt;p&gt;What evidence do we leave behind?&lt;/p&gt;

&lt;p&gt;Can somebody else figure out what happened when I'm not there?&lt;/p&gt;

&lt;p&gt;What does the user see while the system genuinely doesn't know yet?&lt;/p&gt;

&lt;p&gt;I knew what idempotency was before this project.&lt;/p&gt;

&lt;p&gt;I knew what queues were.&lt;/p&gt;

&lt;p&gt;I knew what database locking was.&lt;/p&gt;

&lt;p&gt;I knew what RBAC was.&lt;/p&gt;

&lt;p&gt;I knew what webhooks were.&lt;/p&gt;

&lt;p&gt;I knew what reconciliation meant.&lt;/p&gt;

&lt;p&gt;I just understand &lt;strong&gt;why those things exist&lt;/strong&gt; much better now.&lt;/p&gt;

&lt;p&gt;And that's probably the part I'll remember longest.&lt;/p&gt;

&lt;p&gt;I'm still inside the build.&lt;/p&gt;

&lt;p&gt;I still find awkward UI.&lt;/p&gt;

&lt;p&gt;I still find journeys where a state exists without a good recovery path.&lt;/p&gt;

&lt;p&gt;I still open files and think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why did I let you get this big?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I still have the performance investigation waiting for me.&lt;/p&gt;

&lt;p&gt;There are refactors I've deliberately postponed until the feature pressure drops.&lt;/p&gt;

&lt;p&gt;But when I compare what I thought I was building at the beginning with what exists now, the difference is kind of absurd.&lt;/p&gt;

&lt;p&gt;More than 700 commits.&lt;/p&gt;

&lt;p&gt;More than 200,000 tracked lines.&lt;/p&gt;

&lt;p&gt;Three applications.&lt;/p&gt;

&lt;p&gt;Thirteen shared packages.&lt;/p&gt;

&lt;p&gt;More than 500 tests.&lt;/p&gt;

&lt;p&gt;And considerably more time thinking about failure than I expected when I started.&lt;/p&gt;

&lt;p&gt;I started the project thinking mostly about how to implement features.&lt;/p&gt;

&lt;p&gt;I'm finishing it thinking much more about what the system is actually allowed to promise.&lt;/p&gt;

&lt;p&gt;That's a much more useful change than another framework on my CV.&lt;/p&gt;

&lt;p&gt;I'm almost done.&lt;/p&gt;

&lt;p&gt;For now, back to figuring out why that last thing is still broken.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you've built systems where retries, partial failure, or uncertain state became real problems, I'd genuinely like to hear what changed in the way you engineer.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>fintech</category>
      <category>typescript</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
