<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Object-Design on Brian Faust</title><link>https://wimdy.works/tags/object-design/</link><description>Recent content in Object-Design 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/object-design/rss.xml" rel="self" type="application/rss+xml"/><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 Constructor Should Leave a Usable Object</title><link>https://wimdy.works/thoughts/a-constructor-should-leave-a-usable-object/</link><pubDate>Wed, 01 Dec 2021 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-constructor-should-leave-a-usable-object/</guid><description>&lt;p&gt;This &lt;code&gt;Subscription&lt;/code&gt; exists before it can safely answer a question:&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="nv"&gt;$subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Subscription&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="nv"&gt;$subscription&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$ids&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;next&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="nv"&gt;$subscription&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$customerId&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="nv"&gt;$subscription&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$plans&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$planCode&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="nv"&gt;$subscription&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;trial&amp;#39;&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="nv"&gt;$subscription&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;startsAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$clock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;now&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="nv"&gt;$subscription&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;trialEndsAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$subscription&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;startsAt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;modify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+14 days&amp;#39;&lt;/span&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;Between the first and last line, another method can observe an object without a
plan, status, or start time. Forget the last assignment and an active
subscription can carry a trial end. Reorder the calls and identity may be
consumed for a plan which does not exist.&lt;/p&gt;</description></item><item><title>Strategy Chooses How; Command Records What to Do</title><link>https://wimdy.works/thoughts/strategy-vs-command-pattern/</link><pubDate>Sun, 01 Aug 2021 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/strategy-vs-command-pattern/</guid><description>&lt;p&gt;Two PHP types can have almost identical shapes and still solve different
problems:&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;ShippingRate&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;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Shipment&lt;/span&gt; &lt;span class="nv"&gt;$shipment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Money&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;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;&lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ApplicationCommand&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;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;void&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;Both hide behavior behind a method. Both can be injected and tested. They look
similar enough that “algorithm versus action” sounds like a sufficient answer.
It is not, at least not for application architecture.&lt;/p&gt;
&lt;p&gt;Wait for execution to fail and the difference becomes clearer. A strategy is a
collaborator used while another operation is already in progress. A command
represents work that can acquire its own lifecycle. Once that work must be
queued, retried, deduplicated, authorized, or audited independently, calling it
an implementation choice misses most of the problem.&lt;/p&gt;</description></item><item><title>Composition Over Inheritance Is a Decision, Not a Rule</title><link>https://wimdy.works/thoughts/composition-vs-inheritance/</link><pubDate>Thu, 01 Jul 2021 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/composition-vs-inheritance/</guid><description>&lt;p&gt;“Favor composition over inheritance” is useful advice right up to the point
where it becomes a reflex. Replacing every parent class with an interface and a
service-container binding does not improve the design. It just exchanges one
form of coupling for another, usually with more files.&lt;/p&gt;
&lt;p&gt;What matters is how behavior is allowed to vary. Inheritance fixes variation
into a type hierarchy. Composition puts it behind collaborators that can change
independently. When tax, discounts, shipping, and payment rules evolve along
different axes, forcing them into one family tree gets expensive. When a
subtype genuinely preserves a small, stable parent contract, inheritance may
be the clearest tool available. “Has-a” versus “is-a” is too shallow to decide
between them.&lt;/p&gt;</description></item><item><title>Dependencies Should Be Visible at Construction</title><link>https://wimdy.works/thoughts/dependencies-should-be-visible-at-construction/</link><pubDate>Mon, 01 Jun 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/dependencies-should-be-visible-at-construction/</guid><description>&lt;p&gt;This line created a database 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;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="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;create&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="s1"&gt;&amp;#39;external_id&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;123&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 also performed an HTTP request.&lt;/p&gt;
&lt;p&gt;The factory did not mention a client. The model did not accept one. A
synchronous observer noticed the external identifier, prepared a profile
export, and resolved a connector from global framework state. The connector
then looked up a remote account.&lt;/p&gt;
&lt;p&gt;In tests without a fake, that lookup reached DNS or the network and retried
three times with exponential backoff. Tests which had run in under five
seconds took more than thirty. The slow test was annoying, but it was only the
visible symptom. A local-looking construction operation had acquired an
undeclared remote dependency.&lt;/p&gt;</description></item><item><title>A Service Class Can Become a Procedural Junk Drawer</title><link>https://wimdy.works/thoughts/a-service-class-can-become-a-procedural-junk-drawer/</link><pubDate>Fri, 01 May 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-service-class-can-become-a-procedural-junk-drawer/</guid><description>&lt;p&gt;A web controller contained nearly two thousand lines of shipment creation.
Moving those lines into a class described as a creation orchestrator sounded
like an architectural improvement.&lt;/p&gt;
&lt;p&gt;The new class contained nearly two thousand lines of shipment creation.&lt;/p&gt;
&lt;p&gt;The first move was still useful. It removed the procedure from the HTTP
adapter. What it did not do was make the procedure cohesive. Validation,
payment gating, draft persistence, provider booking, tracking, document
creation, response mapping, notifications, and indexing still changed inside
one file. &lt;code&gt;Service&lt;/code&gt; and &lt;code&gt;orchestrator&lt;/code&gt; are roles only when the code earns those
names. Otherwise they are folders for procedures we no longer want in
controllers.&lt;/p&gt;</description></item><item><title>A Primitive Can Hide a Domain Decision</title><link>https://wimdy.works/thoughts/a-primitive-can-hide-a-domain-decision/</link><pubDate>Wed, 01 Apr 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-primitive-can-hide-a-domain-decision/</guid><description>&lt;p&gt;Uppercasing a country code looks like normalization:&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;return&lt;/span&gt; &lt;span class="nx"&gt;mb_strtoupper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&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 makes &lt;code&gt;fi&lt;/code&gt; and &lt;code&gt;FI&lt;/code&gt; equal. It also turns &lt;code&gt;finland&lt;/code&gt; into &lt;code&gt;FINLAND&lt;/code&gt; and leaves
&lt;code&gt;FIN&lt;/code&gt; looking plausible.&lt;/p&gt;
&lt;p&gt;The string is tidier, not safer. The function has changed its shape without
answering the domain question: is this a supported country identity in the
representation this boundary promises? A primitive becomes expensive when
every caller must remember that answer. The reason to introduce a domain type
isn&amp;rsquo;t that strings are unfashionable. It is that the scalar no longer carries
enough of the contract.&lt;/p&gt;</description></item><item><title>Boolean Parameters Hide Different Operations</title><link>https://wimdy.works/thoughts/boolean-parameters-hide-different-operations/</link><pubDate>Sun, 01 Mar 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/boolean-parameters-hide-different-operations/</guid><description>&lt;p&gt;This method call is legal:&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="nv"&gt;$exporter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&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 is also semantically wrong.&lt;/p&gt;
&lt;p&gt;The first boolean in the exporter meant “source billing details from
the effective payer.” The payload still targeted the original account and kept
that account&amp;rsquo;s operational fields. The second boolean meant “the source&amp;rsquo;s
billing mode is an authoritative instruction.”&lt;/p&gt;
&lt;p&gt;An effective-payer export described who pays; it was not allowed to change the
original account&amp;rsquo;s billing mode. The &lt;code&gt;true, true&lt;/code&gt; combination therefore had to
be neutralized by a later guard.&lt;/p&gt;</description></item><item><title>Invalid State Should Be Difficult to Construct</title><link>https://wimdy.works/thoughts/invalid-state-should-be-difficult-to-construct/</link><pubDate>Sat, 01 Feb 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/invalid-state-should-be-difficult-to-construct/</guid><description>&lt;p&gt;A wallet reservation held one amount while a booking was in progress. When the
booking later reached the billing system, an imported record could contain no
price. The reservation knew how much money had been held, but not how that
amount divided into net price and VAT.
There was no authoritative way to create the final sale from the hold alone;
inferring a tax rate would turn a guess into accounting data.&lt;/p&gt;</description></item><item><title>A Class Name Should Describe a Role</title><link>https://wimdy.works/thoughts/a-class-name-should-describe-a-role/</link><pubDate>Wed, 01 Jan 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-class-name-should-describe-a-role/</guid><description>&lt;p&gt;Several classes in one application had almost the same shape. Each extended a
connection manager, read named configuration, and delegated object creation to
a factory. One managed payment gateways, another rating clients, and another
booking clients.&lt;/p&gt;
&lt;p&gt;The shared abstraction made their code look alike. Their callers were asking
different questions.&lt;/p&gt;
&lt;p&gt;Payment callers needed a gateway with a given name. Rating callers sometimes
needed to decide which connection matched a shipment. Booking callers selected
connections from different rules for creating, collecting, and cancelling,
then assembled operation-specific services around the result.&lt;/p&gt;</description></item></channel></rss>