<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Architecture on Brian Faust</title><link>https://wimdy.works/tags/architecture/</link><description>Recent content in Architecture 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://wimdy.works/tags/architecture/rss.xml" rel="self" type="application/rss+xml"/><item><title>Retire the Browser, Keep the Test Runner</title><link>https://wimdy.works/thoughts/retire-the-browser-keep-the-test-runner/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/retire-the-browser-keep-the-test-runner/</guid><description>&lt;p&gt;The Go port deliberately dropped the browser UI. The API tester was the awkward
exception: not its pages, but the program behind them. It could create a test
configuration, publish it, make real API calls, extract identifiers, wait for a
result and clean up afterward. Deleting its screen was straightforward.
Deleting all of that would have thrown away a useful part of the migration.&lt;/p&gt;
&lt;p&gt;Replacing it with a CLI also exposed distinctions the screen could make easy
to overlook. An HTTP 200 response could mean a failed test step. A failed run
could still be a successfully produced command result. A saved step record
didn&amp;rsquo;t mean its API call would never execute again. Those weren&amp;rsquo;t reasons to
redesign the tester during the port; they were behavior to understand before
moving its entry point.&lt;/p&gt;</description></item><item><title>When the Framework Sets the Latency Floor</title><link>https://wimdy.works/thoughts/when-the-framework-sets-the-latency-floor/</link><pubDate>Sat, 01 Aug 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/when-the-framework-sets-the-latency-floor/</guid><description>&lt;p&gt;By the time I considered moving the pricing backend to Go, the PHP version
already had table-driven pricing, prepared runtime projections, candidate
indexes, and substantial cache work. Those changes mattered. They didn&amp;rsquo;t
remove the request cost I was still seeing before business processing:
roughly 50–100 milliseconds on the conventional PHP/Laravel path, reduced to
roughly 30–40 milliseconds with Octane in the setup I tested.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t want to spend the rest of the project squeezing a little more out
of that same path. Go was a better fit for the request and computation work,
particularly as the pricing datasets grew. Performance, resource consumption,
and stability drove the choice. The interesting engineering question became
how to replace the runtime without quietly replacing the application too.&lt;/p&gt;</description></item><item><title>The Order of Charges Changes the Price</title><link>https://wimdy.works/thoughts/the-order-of-charges-changes-the-price/</link><pubDate>Wed, 15 Apr 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/the-order-of-charges-changes-the-price/</guid><description>&lt;p&gt;Two row orders can produce the same €35 quote while disagreeing about the
fuel charge. In one, fuel is €2.70; in the other, it&amp;rsquo;s €2.40. A minimum-charge
top-up hides the difference in the total. Assert only that total and the test
passes either way.&lt;/p&gt;
&lt;p&gt;The difference comes from when the fuel row sees a handling charge. The
percentage reads the components already built, not a final basket reconstructed
after every row has run. Scope also limits which of those components it can
use. A shipment percentage doesn&amp;rsquo;t automatically include parcel charges just
because they contribute to the same quote.&lt;/p&gt;</description></item><item><title>How a Shipping Quote Gets Its Price</title><link>https://wimdy.works/thoughts/a-shipping-price-is-a-calculation-not-a-lookup/</link><pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-shipping-price-is-a-calculation-not-a-lookup/</guid><description>&lt;p&gt;A parcel weighs two kilograms. The tariff has a price for two kilograms.
Returning that price sounds reasonable. It can also be wrong before the
lookup finishes: the tariff may charge for volume, the account may have a
negotiated transport rate, and handling may apply to each parcel rather than
once to the shipment.&lt;/p&gt;
&lt;p&gt;I built a private logistics rating engine around those interactions. It
stayed private rather than becoming a commercial product. What made it
interesting was that most of the difficulty came from pricing itself. I
could work on the model without first accommodating years of
unrelated business exceptions. There was still plenty to get wrong.&lt;/p&gt;</description></item><item><title>Boundaries Follow Work Without Becoming Destiny</title><link>https://wimdy.works/thoughts/boundaries-follow-work-without-becoming-destiny/</link><pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/boundaries-follow-work-without-becoming-destiny/</guid><description>&lt;p&gt;We had a small team, so every service was officially owned by everyone. In
practice, a developer could finish five hours of work in a specialist
application and then spend three weeks integrating and testing it against the
legacy API.&lt;/p&gt;
&lt;p&gt;The delay was not a queue in front of another team. There was no other team. It
came from a system which had evolved through changing commercial priorities,
agencies, freelancers, and eventually an internal development team. Plenty of
individual decisions made sense when they were made. Their combined cost still
landed on the next person trying to change anything.&lt;/p&gt;</description></item><item><title>Enforce Architecture at the Cheapest Reliable Layer</title><link>https://wimdy.works/thoughts/enforce-architecture-at-the-cheapest-reliable-layer/</link><pubDate>Sun, 01 Sep 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/enforce-architecture-at-the-cheapest-reliable-layer/</guid><description>&lt;p&gt;The rule says Domain must not depend on Infrastructure.&lt;/p&gt;
&lt;p&gt;Code review misses this reference:&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;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;\Illuminate\Database\Eloquent\Model&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;A quick substring scan catches it. The same scan also rejects a harmless
docblock which names &lt;code&gt;EloquentOrderRepository&lt;/code&gt; while explaining what Domain
must not do.&lt;/p&gt;
&lt;p&gt;The first check is unreliable because a person must notice every occurrence.
The second is unreliable because text is not a PHP dependency. A token-aware
source check catches the executable reference and ignores the prose.&lt;/p&gt;</description></item><item><title>Name the Independent Thing</title><link>https://wimdy.works/thoughts/name-the-independent-thing/</link><pubDate>Thu, 01 Aug 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/name-the-independent-thing/</guid><description>&lt;p&gt;Both diagrams say &lt;code&gt;microservices&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The first has four HTTP endpoints and four repositories. They share one
database, move on one release train, page one on-call rotation, and require a
coordinated deployment when the shared schema changes.&lt;/p&gt;
&lt;p&gt;The second can release Payments without Ordering, lose the Payments process
without losing unrelated Ordering work, rotate payment credentials inside one
data zone, and send repair to a staffed Payments owner.&lt;/p&gt;
&lt;p&gt;The label is identical. The operating contract is not.&lt;/p&gt;</description></item><item><title>A Module Is Cheaper Than a Network Boundary</title><link>https://wimdy.works/thoughts/a-module-is-cheaper-than-a-network-boundary/</link><pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-module-is-cheaper-than-a-network-boundary/</guid><description>&lt;p&gt;The payment was captured. Ordering does not know that.&lt;/p&gt;
&lt;p&gt;Its request reached Payments, the capture became durable, and the response
disappeared on the way back. Ordering now has a pending order and a timeout.
Retrying without a stable identity may charge twice. Treating the timeout as a
decline may abandon a real capture.&lt;/p&gt;
&lt;p&gt;Inside one process and transaction, that particular ambiguity does not exist.
The payment and order commit together or roll back together. Moving Payments
across a network has bought a separate release and failure boundary. It has
also replaced a return value with a protocol for identity, uncertainty, and
repair.&lt;/p&gt;</description></item><item><title>Decompose by Change, Not by Layer</title><link>https://wimdy.works/thoughts/decompose-by-change-not-by-layer/</link><pubDate>Sat, 01 Jun 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/decompose-by-change-not-by-layer/</guid><description>&lt;p&gt;A cancellation rule changes: refuse cancellation after warehouse handoff.&lt;/p&gt;
&lt;p&gt;In a conventional layered application, the work touches a controller, an
application service, a domain policy, a repository, a migration, and tests. If
the organization mirrors those directories, one business decision becomes a
project for API, backend, and database teams.&lt;/p&gt;
&lt;p&gt;The next change adds a covering index for order lookup. It touches only the
persistence layer and changes no business decision.&lt;/p&gt;</description></item><item><title>A Version Column Is Not Concurrency Control</title><link>https://wimdy.works/thoughts/a-version-column-is-not-concurrency-control/</link><pubDate>Sun, 01 Jan 2023 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-version-column-is-not-concurrency-control/</guid><description>&lt;p&gt;A capacity pool has ten units available. Two workers load it before either one
writes:&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;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&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;worker A loads: version 0, reserved 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;worker B loads: version 0, reserved 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;worker A allocates 6 to allocation-a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;worker B allocates 6 to allocation-b&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 decisions pass the aggregate&amp;rsquo;s rule. Each worker sees ten remaining
units. They cannot both belong to one ordered history: after either allocation,
only four remain.&lt;/p&gt;</description></item><item><title>Loading State Is Not Creating It Again</title><link>https://wimdy.works/thoughts/loading-state-is-not-creating-it-again/</link><pubDate>Thu, 01 Dec 2022 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/loading-state-is-not-creating-it-again/</guid><description>&lt;p&gt;An order placed in 2024 is still in the database:&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;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&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;number=LEGACY-42
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;status=placed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fulfil_by=2024-12-20T10:00:00Z
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;version=7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Today, new web orders need a number beginning with &lt;code&gt;WEB-&lt;/code&gt; and a fulfilment
deadline in the future. Pass that stored row through today&amp;rsquo;s creation factory
and one of two bad things happens. The factory rejects valid history, or the
load pretends that placement happened again and records a new &lt;code&gt;OrderPlaced&lt;/code&gt;
fact.&lt;/p&gt;</description></item><item><title>A Mapper Connects Two Honest Models</title><link>https://wimdy.works/thoughts/a-mapper-connects-two-honest-models/</link><pubDate>Tue, 01 Nov 2022 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-mapper-connects-two-honest-models/</guid><description>&lt;p&gt;An order in the domain has an &lt;code&gt;OrderNumber&lt;/code&gt;, a &lt;code&gt;Money&lt;/code&gt; total, typed lines, and
an operation named &lt;code&gt;place()&lt;/code&gt;. The same order in a relational database has an
&lt;code&gt;orders&lt;/code&gt; row, several &lt;code&gt;order_lines&lt;/code&gt; rows, integer minor units, a nullable
deadline, check constraints, a foreign key, and an index used by fulfilment.&lt;/p&gt;
&lt;p&gt;Forcing those shapes to match exactly damages one side or the other.&lt;/p&gt;
&lt;p&gt;Make the domain object mirror the tables and it acquires column names, nullable
intermediate state, relationship-loading concerns, and methods shaped around
persistence. Store the object as one opaque document and the database loses
straightforward constraints and access paths required by the selected query.&lt;/p&gt;</description></item><item><title>A Repository Is a Domain Collection, Not an ORM Wrapper</title><link>https://wimdy.works/thoughts/what-repositories-really-do/</link><pubDate>Sat, 01 Oct 2022 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/what-repositories-really-do/</guid><description>&lt;p&gt;This interface appears in many Laravel codebases:&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;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Repository&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Collection&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&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;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;Model&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Model&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&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;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Model&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bool&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;It hides the word Eloquent and preserves everything else about Eloquent&amp;rsquo;s
world: models, attribute arrays, record IDs, and CRUD operations. Callers still
need to know which fields are writable, which relationships are loaded, and
which operations preserve domain rules.&lt;/p&gt;
&lt;p&gt;That is not a useful persistence boundary. It is a smaller, less capable ORM,
which is an odd thing to spend an interface on.&lt;/p&gt;</description></item><item><title>A Composition Root Makes Choices Visible</title><link>https://wimdy.works/thoughts/a-composition-root-makes-choices-visible/</link><pubDate>Thu, 01 Sep 2022 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-composition-root-makes-choices-visible/</guid><description>&lt;p&gt;Remove the &lt;code&gt;ProductPrices&lt;/code&gt; binding from a Laravel service provider, then ask
the container for &lt;code&gt;QuoteProduct&lt;/code&gt;. Laravel refuses to construct it:&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;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&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;Target [App\Application\ProductPrices] is not instantiable while building
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[App\Application\QuoteProduct].&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Move the same lookup into &lt;code&gt;QuoteProduct::execute()&lt;/code&gt; and construction succeeds.
That can look like simplification because the constructor now says nothing.
The object graph did not become simpler. Part of it moved into method code and
configuration, where Laravel cannot reject it until a quote reaches that line.&lt;/p&gt;</description></item><item><title>A Hexagon Is Not a Directory Layout</title><link>https://wimdy.works/thoughts/a-hexagon-is-not-a-directory-layout/</link><pubDate>Mon, 01 Aug 2022 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-hexagon-is-not-a-directory-layout/</guid><description>&lt;p&gt;A Laravel controller calls a catalogue API, reads &lt;code&gt;price_cents&lt;/code&gt;, and returns a
quote. Moving that HTTP code into &lt;code&gt;Application/QuoteProduct.php&lt;/code&gt; makes the
controller thinner. It also leaves the application service coupled to
Laravel&amp;rsquo;s HTTP client, the provider URL, and the provider&amp;rsquo;s JSON.&lt;/p&gt;
&lt;p&gt;The tree looks more architectural. The source dependencies have not moved.
That is architecture by folder name, and it buys almost nothing.&lt;/p&gt;
&lt;p&gt;That difference becomes visible when the catalogue replaces &lt;code&gt;price_cents&lt;/code&gt;
with &lt;code&gt;price.amount_minor&lt;/code&gt;. A folder name cannot decide where the resulting
change lands. A useful hexagonal boundary can: application policy defines the
conversation it needs, while an outside adapter owns the provider mechanism.&lt;/p&gt;</description></item><item><title>A Design Principle Is a Compressed Failure History</title><link>https://wimdy.works/thoughts/a-design-principle-is-a-compressed-failure-history/</link><pubDate>Fri, 01 Jan 2021 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-design-principle-is-a-compressed-failure-history/</guid><description>&lt;p&gt;“This violates the single-responsibility principle” is not an explanation. It
may be the conclusion of one, but the useful part starts earlier:&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;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&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;Lookup policy, selection policy, and construction policy change for different
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;reasons. One class owns all three. A lookup change therefore makes selection
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tests and construction code part of the review surface.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Once that reasoning exists, “single responsibility” gives it a short name that
another engineer can retrieve. Without the reasoning, it is just a respectable
label which can justify almost any refactor.&lt;/p&gt;</description></item><item><title>Design Becomes Valuable When the Next Change Arrives</title><link>https://wimdy.works/thoughts/design-becomes-valuable-when-the-next-change-arrives/</link><pubDate>Tue, 01 Dec 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/design-becomes-valuable-when-the-next-change-arrives/</guid><description>&lt;p&gt;One migration removed four vendor packages. Two payment drivers, their client
interfaces, and their factory interfaces did not change; the concrete clients
changed substantially.&lt;/p&gt;
&lt;p&gt;Two application controllers were less fortunate. They had constructed a
vendor connection directly to manage webhooks, so the replacement had to add a
small webhook-management role and move the remote protocol behind it.&lt;/p&gt;
&lt;p&gt;The same migration produced both results: some earlier seams absorbed the
change, while other paths showed exactly where the vendor had leaked through.
That tells us more than declaring the design “clean.” A design makes a
prediction about future change, and the next real change tells us which parts
of that prediction were right.&lt;/p&gt;</description></item><item><title>A Directory Map Is Not a Redesign Plan</title><link>https://wimdy.works/thoughts/a-directory-map-is-not-a-redesign-plan/</link><pubDate>Tue, 01 Jan 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-directory-map-is-not-a-redesign-plan/</guid><description>&lt;p&gt;A reorganization plan found dozens of domain directories and several older
modules. It assigned them to a set of proposed bounded contexts, selected an
apparently isolated location-and-address context as a safe starting point, and
described where its files should move.&lt;/p&gt;
&lt;p&gt;The map was detailed and tidy. It was also missing the part which determined
whether the boundary was real.&lt;/p&gt;
&lt;p&gt;In the running system, the legacy application kept service-point policy while
calling separate location and postal applications. Location lookup called
postal lookup. Each application owned different state. Booking, validation,
labels, returns, settings, administration, and health checks all consumed some
part of the flow.&lt;/p&gt;</description></item><item><title>A Capability Interface Is Smaller Than a Vendor Client</title><link>https://wimdy.works/thoughts/a-capability-interface-is-smaller-than-a-vendor-client/</link><pubDate>Sat, 01 Dec 2018 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-capability-interface-is-smaller-than-a-vendor-client/</guid><description>&lt;p&gt;A controller needs to register an order webhook. Instead, 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. That is a lot of vendor vocabulary for one product decision.&lt;/p&gt;
&lt;p&gt;The test is no better. It has to intercept construction of that concrete
connection and mock a dynamic property chain. Two useful behavior tests ended
up skipped with the same explanation: the code needed an interface and
dependency injection.&lt;/p&gt;</description></item></channel></rss>