<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Integration on Brian Faust</title><link>https://vacua.dev/tags/integration/</link><description>Recent content in Integration on Brian Faust</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Mon, 01 Jan 0001 00:00:00 +0000</lastBuildDate><atom:link href="https://vacua.dev/tags/integration/rss.xml" rel="self" type="application/rss+xml"/><item><title>Parallel Implementations Are Not a Replacement Seam</title><link>https://vacua.dev/thoughts/parallel-implementations-are-not-a-replacement-seam/</link><pubDate>Mon, 01 Apr 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/parallel-implementations-are-not-a-replacement-seam/</guid><description>&lt;p&gt;An old shipping integration and its proposed replacement lived beside each
other. Both had an interface. Both had a driver, request builders, and tests.
That looked like a seam.&lt;/p&gt;
&lt;p&gt;When the replacement was finally made canonical, the change still crossed
dozens of files. The old interface disappeared, the new one moved, the
canonical driver changed its pickup and shipment flows, and a parallel driver
tree was deleted. Follow-up work then corrected a request shape and added
serialization tests.&lt;/p&gt;</description></item><item><title>A Capability Interface Is Smaller Than a Vendor Client</title><link>https://vacua.dev/thoughts/a-capability-interface-is-smaller-than-a-vendor-client/</link><pubDate>Sat, 01 Dec 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-capability-interface-is-smaller-than-a-vendor-client/</guid><description>&lt;p&gt;A controller needs to register an order webhook. It constructs a vendor SDK
connection, accesses a dynamic resource property, calls its creation method,
names the remote event, chooses response fields, and catches the SDK&amp;rsquo;s base
exception.&lt;/p&gt;
&lt;p&gt;Its test needs to intercept construction of that concrete connection and mock a
dynamic property chain. The two useful behavior tests are skipped with the same
explanation: the code needs an interface and dependency injection.&lt;/p&gt;</description></item><item><title>Renaming a Field Is a Protocol Migration</title><link>https://vacua.dev/thoughts/renaming-a-field-is-a-protocol-migration/</link><pubDate>Thu, 01 Nov 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/renaming-a-field-is-a-protocol-migration/</guid><description>&lt;p&gt;An API changes &lt;code&gt;serviceIds&lt;/code&gt; to &lt;code&gt;deliveryOptionIds&lt;/code&gt;. The new name describes the
domain better. The implementation becomes easier to read.&lt;/p&gt;
&lt;p&gt;An existing client still sends:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;serviceIds&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;express&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The request now fails validation before any application code runs.&lt;/p&gt;
&lt;p&gt;Nothing about the client&amp;rsquo;s intent changed. The server chose a cleaner name and
turned that preference into a breaking protocol change.&lt;/p&gt;
&lt;p&gt;Once another process sends a field, renaming it is no longer a refactor. It is
a migration involving deployed callers whose release schedule the server may
not control.&lt;/p&gt;</description></item><item><title>A Cache Must Distinguish Empty from Unavailable</title><link>https://vacua.dev/thoughts/a-cache-must-distinguish-empty-from-unavailable/</link><pubDate>Mon, 01 Oct 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-cache-must-distinguish-empty-from-unavailable/</guid><description>&lt;p&gt;A cached search returns two pickup locations. One location is deleted locally,
but the search keeps returning both.&lt;/p&gt;
&lt;p&gt;The tempting repair is a shorter time to live. That trades a known correctness
bug for a shorter known correctness bug. The system already knows the cached
answer is false; waiting five minutes instead of an hour does not make it less
false.&lt;/p&gt;
&lt;p&gt;The opposite failure looks similar. A remote lookup returns nothing during an
outage. Replacing the cache with an empty list would remove locations which may
still exist.&lt;/p&gt;</description></item><item><title>Partial Work Needs a Child Ledger</title><link>https://vacua.dev/thoughts/partial-work-needs-a-child-ledger/</link><pubDate>Sat, 01 Sep 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/partial-work-needs-a-child-ledger/</guid><description>&lt;p&gt;A queue job receives 1,000 events in one remote response. It dispatches 600
child jobs, then times out.&lt;/p&gt;
&lt;p&gt;The parent row says processing failed. Some children have already finished.
Others are waiting in the queue. The remaining 400 were never dispatched. A
counter can report 573 completed events, but it cannot say which 573 they were.&lt;/p&gt;
&lt;p&gt;Running the parent again may duplicate the first 600. Leaving it alone loses
the last 400. Neither choice is defensible because the system retained a total
and discarded the membership behind it.&lt;/p&gt;</description></item><item><title>Retry Budgets Multiply Across Layers</title><link>https://vacua.dev/thoughts/retry-budgets-multiply-across-layers/</link><pubDate>Wed, 01 Aug 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/retry-budgets-multiply-across-layers/</guid><description>&lt;p&gt;An HTTP connector allows three attempts. The queue job calling it also allows
three attempts.&lt;/p&gt;
&lt;p&gt;When the remote service keeps failing, the job does not make three calls. It
makes nine:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;job attempt 1 -&amp;gt; connector calls 1, 2, 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;job attempt 2 -&amp;gt; connector calls 4, 5, 6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;job attempt 3 -&amp;gt; connector calls 7, 8, 9&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Both settings look modest in isolation. The remote system receives their
product.&lt;/p&gt;</description></item><item><title>A Rate Limit Belongs to All the Workers Sharing It</title><link>https://vacua.dev/thoughts/a-rate-limit-belongs-to-all-the-workers-sharing-it/</link><pubDate>Sun, 01 Jul 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-rate-limit-belongs-to-all-the-workers-sharing-it/</guid><description>&lt;p&gt;One polling job receives a 429 response saying to try again in fifteen seconds.
It releases itself for fifteen seconds.&lt;/p&gt;
&lt;p&gt;During that pause, twenty other workers poll the same provider. They have not
seen the response, so each sends another request into the closed window.&lt;/p&gt;
&lt;p&gt;The first job obeyed the limit. The application did not.&lt;/p&gt;
&lt;h2 id="find-the-scope-before-choosing-the-delay"&gt;Find the scope before choosing the delay&lt;/h2&gt;
&lt;p&gt;A rate limit applies to some shared identity: an API credential, account,
tenant, endpoint family, source address, or a combination of them. Every worker
using that identity spends the same remote capacity.&lt;/p&gt;</description></item><item><title>An Idempotency Key Is Not an Identity</title><link>https://vacua.dev/thoughts/an-idempotency-key-is-not-an-identity/</link><pubDate>Fri, 01 Jun 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/an-idempotency-key-is-not-an-identity/</guid><description>&lt;p&gt;A booking test sends the same request twice with the same idempotency key. The
remote booking service is called once. Both callers receive the same successful
response.&lt;/p&gt;
&lt;p&gt;Then one field changes while the key stays the same. The remote service is
still called once, but the second request receives a conflict instead of the
first result.&lt;/p&gt;
&lt;p&gt;The key did not establish sameness. It only gave the application somewhere to
look.&lt;/p&gt;</description></item><item><title>A Webhook Delivery Is Not a Unique Event</title><link>https://vacua.dev/thoughts/a-webhook-delivery-is-not-a-unique-event/</link><pubDate>Tue, 01 May 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-webhook-delivery-is-not-a-unique-event/</guid><description>&lt;p&gt;A subscription callback had already created this local record:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;carrier: external-freight
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;subscription_id: sub-demo-17
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;secret: old-secret
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;expires_at: 2018-05-08T12:00:00Z&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The sender delivered another validation message for &lt;code&gt;sub-demo-17&lt;/code&gt;, now with a
new secret and expiry. The handler tried to insert a second row. A unique
constraint rejected it, so the activation step never ran.&lt;/p&gt;
&lt;p&gt;The database was right. The handler had mistaken another delivery for another
subscription.&lt;/p&gt;
&lt;h2 id="start-with-the-identity-the-sender-repeated"&gt;Start with the identity the sender repeated&lt;/h2&gt;
&lt;p&gt;A webhook request is evidence that a message was delivered. It is not evidence
that this message has never arrived before.&lt;/p&gt;</description></item><item><title>Map External Data Before It Enters Your Model</title><link>https://vacua.dev/thoughts/map-external-data-before-it-enters-your-model/</link><pubDate>Sun, 01 Apr 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/map-external-data-before-it-enters-your-model/</guid><description>&lt;p&gt;A payment callback arrives with these fields:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;COMPLETED&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;amount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;7.79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;currency&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;EUR&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;reference&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;session-demo-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;transaction&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;remote-demo-31&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;message_time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-04-01T09:15:00Z&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The application does not need a &lt;code&gt;COMPLETED&lt;/code&gt; string or a floating-point amount.
It needs a trusted result: which local operation completed, for how much, in
which currency, under which remote identity, and at what instant.&lt;/p&gt;
&lt;p&gt;Passing the decoded array into business code postpones that translation. It
does not avoid it.&lt;/p&gt;
&lt;h2 id="authenticity-comes-before-interpretation"&gt;Authenticity comes before interpretation&lt;/h2&gt;
&lt;p&gt;The retained callback boundary receives a JSON string and a message
authentication code. It rejects a missing or invalid code before the payment
driver finalizes anything.&lt;/p&gt;</description></item><item><title>An HTTP 200 Does Not Mean the Operation Succeeded</title><link>https://vacua.dev/thoughts/an-http-200-does-not-mean-the-operation-succeeded/</link><pubDate>Mon, 01 Jan 2018 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/an-http-200-does-not-mean-the-operation-succeeded/</guid><description>&lt;p&gt;Two requests reach the same JSON application.&lt;/p&gt;
&lt;p&gt;The first lacks authentication. The server returns HTTP 200 with this body:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Unauthenticated&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The second is authenticated but lacks permission to browse a collection. It
also returns HTTP 200, this time with an empty array:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Both behaviors are pinned by retained tests. Neither can be interpreted from
the status code alone. Worse, the empty array is indistinguishable from an
authorized search which genuinely found nothing.&lt;/p&gt;</description></item><item><title>An Identifier Should Outlive Its Storage Engine</title><link>https://vacua.dev/thoughts/an-identifier-should-outlive-its-storage-engine/</link><pubDate>Sat, 01 Jul 2017 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/an-identifier-should-outlive-its-storage-engine/</guid><description>&lt;p&gt;A billing export once had enough information to reconstruct a missing account,
but not enough to name it.&lt;/p&gt;
&lt;p&gt;The account still existed in an older system. Its stable identifier reached the
export action. No matching row existed in the newer application&amp;rsquo;s user table,
so the action built a temporary object with safe fallback details. That object
had a synthetic local identifier, but its export identifier remained null.
Later, a queued data object required that identifier to be a string and
rejected the payload.&lt;/p&gt;</description></item></channel></rss>