<?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://vacua.dev/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://vacua.dev/tags/architecture/rss.xml" rel="self" type="application/rss+xml"/><item><title>Boundaries Follow Work Without Becoming Destiny</title><link>https://vacua.dev/boundaries-follow-work-without-becoming-destiny/</link><pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate><guid>https://vacua.dev/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 across years, changing commercial
priorities, agencies, freelancers, and eventually an internal development
team. Each period left decisions which made sense under its own pressure. Their
combined cost appeared in the next change.&lt;/p&gt;</description></item><item><title>Enforce Architecture at the Cheapest Reliable Layer</title><link>https://vacua.dev/enforce-architecture-at-the-cheapest-reliable-layer/</link><pubDate>Sun, 01 Sep 2024 00:00:00 +0000</pubDate><guid>https://vacua.dev/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;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://vacua.dev/name-the-independent-thing/</link><pubDate>Thu, 01 Aug 2024 00:00:00 +0000</pubDate><guid>https://vacua.dev/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://vacua.dev/a-module-is-cheaper-than-a-network-boundary/</link><pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate><guid>https://vacua.dev/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://vacua.dev/decompose-by-change-not-by-layer/</link><pubDate>Sat, 01 Jun 2024 00:00:00 +0000</pubDate><guid>https://vacua.dev/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://vacua.dev/a-version-column-is-not-concurrency-control/</link><pubDate>Sun, 01 Jan 2023 00:00:00 +0000</pubDate><guid>https://vacua.dev/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;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://vacua.dev/loading-state-is-not-creating-it-again/</link><pubDate>Thu, 01 Dec 2022 00:00:00 +0000</pubDate><guid>https://vacua.dev/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;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;
&lt;p&gt;Loading is not a second attempt at creation. Creation decides whether a new
transition is allowed now. Reconstitution restores state which was accepted
before, without claiming that the transition just occurred.&lt;/p&gt;</description></item><item><title>A Mapper Connects Two Honest Models</title><link>https://vacua.dev/a-mapper-connects-two-honest-models/</link><pubDate>Tue, 01 Nov 2022 00:00:00 +0000</pubDate><guid>https://vacua.dev/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://vacua.dev/what-repositories-really-do/</link><pubDate>Sat, 01 Oct 2022 00:00:00 +0000</pubDate><guid>https://vacua.dev/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;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.&lt;/p&gt;</description></item><item><title>A Composition Root Makes Choices Visible</title><link>https://vacua.dev/a-composition-root-makes-choices-visible/</link><pubDate>Thu, 01 Sep 2022 00:00:00 +0000</pubDate><guid>https://vacua.dev/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;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.
The missing dependency does not become visible until a quote request reaches
that line. The application object looks simpler because its constructor says
nothing. Its object graph is not simpler; part of the graph has moved into
method code and configuration.&lt;/p&gt;</description></item><item><title>A Hexagon Is Not a Directory Layout</title><link>https://vacua.dev/a-hexagon-is-not-a-directory-layout/</link><pubDate>Mon, 01 Aug 2022 00:00:00 +0000</pubDate><guid>https://vacua.dev/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.&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></channel></rss>