<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Changing Existing Software on Brian Faust</title><link>https://vacua.dev/series/changing-existing-software/</link><description>Recent content in Changing Existing Software 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/series/changing-existing-software/rss.xml" rel="self" type="application/rss+xml"/><item><title>Stored Data Is a Caller You Cannot Find with Code Search</title><link>https://vacua.dev/thoughts/stored-data-is-a-caller-you-cannot-find-with-code-search/</link><pubDate>Sun, 01 Dec 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/stored-data-is-a-caller-you-cannot-find-with-code-search/</guid><description>&lt;p&gt;An enum migration looked mechanical. A package-backed value object became a
native PHP backed enum. The domain concept stayed the same, the new cases were
easy to search, and current code wrote the canonical values.&lt;/p&gt;
&lt;p&gt;Then an older row loaded a lowercase value. The native enum expected the same
word with different casing and rejected it during hydration.&lt;/p&gt;
&lt;p&gt;The missing caller was not in the source tree. It was sitting in the database.&lt;/p&gt;</description></item><item><title>The Strangler Pattern Still Needs an Exit Plan</title><link>https://vacua.dev/thoughts/the-strangler-pattern-still-needs-an-exit-plan/</link><pubDate>Fri, 01 Nov 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/the-strangler-pattern-still-needs-an-exit-plan/</guid><description>&lt;p&gt;A replacement proof of concept imported one provider&amp;rsquo;s postal data into
PostgreSQL and served one existing JSON-RPC search method from a Go binary. A
checked-in request and response fixture preserved field names, nulls, empty
strings, ordering, and identifier shape. Import failure left the last committed
dataset intact.&lt;/p&gt;
&lt;p&gt;The slice deliberately excluded every other provider, the other RPC methods,
queues, schedulers, authentication, caching, operational interfaces, and
traffic cutover.&lt;/p&gt;
&lt;p&gt;That is a good proof of one vertical path. It is not a plan for retiring the
system around it.&lt;/p&gt;</description></item><item><title>Deprecation Is a Migration, Not a Warning</title><link>https://vacua.dev/thoughts/deprecation-is-a-migration-not-a-warning/</link><pubDate>Tue, 01 Oct 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/deprecation-is-a-migration-not-a-warning/</guid><description>&lt;p&gt;A billing rewrite introduced durable export records before removing several
legacy queue jobs. Each new worker carried a record identifier. The export
type, payload snapshot, attempts, failure details, and retry state lived in the
database. A registry selected the current handler.&lt;/p&gt;
&lt;p&gt;That order matters. A queued object is not merely code waiting to run. Its
serialized class name and properties are data. Delete the class while an old
payload remains, and PHP can deserialize it as an incomplete object which no
longer has the method the worker expects.&lt;/p&gt;</description></item><item><title>Static Analysis Turns Assumptions into Questions</title><link>https://vacua.dev/thoughts/static-analysis-turns-assumptions-into-questions/</link><pubDate>Sun, 01 Sep 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/static-analysis-turns-assumptions-into-questions/</guid><description>&lt;p&gt;A domain action caught any exception and returned &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, or an empty
array. Elsewhere, reusable services read the current request, tenant, and
locale from mutable framework context.&lt;/p&gt;
&lt;p&gt;Both patterns can be intentional. A fallback may be the contract at a
presentation boundary. Ambient context is normal in the code which receives a
web request. The problem appears when domain failure becomes indistinguishable
from a valid negative answer, or when a reusable service silently depends on
which request happened to call it.&lt;/p&gt;</description></item><item><title>A Code Review Should Test the Decision, Not the Author</title><link>https://vacua.dev/thoughts/a-code-review-should-test-the-decision-not-the-author/</link><pubDate>Thu, 01 Aug 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-code-review-should-test-the-decision-not-the-author/</guid><description>&lt;p&gt;One booking flow could trigger the same automatic wallet refill twice. The
first refill happened while booking. A later debit event ran the ordinary
refill check again.&lt;/p&gt;
&lt;p&gt;The retained correction wrote a short-lived marker per account containing the
pending amount. When a matching shipment debit arrived, it consumed the marker
and skipped one refill. Focused tests covered the expected sequence.&lt;/p&gt;
&lt;p&gt;That is enough to begin a review, not finish one. The decision is not “is this
cache helper elegant?” It is:&lt;/p&gt;</description></item><item><title>A Small Change Limits the States a Reviewer Must Hold</title><link>https://vacua.dev/thoughts/a-small-change-limits-the-states-a-reviewer-must-hold/</link><pubDate>Mon, 01 Jul 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-small-change-limits-the-states-a-reviewer-must-hold/</guid><description>&lt;p&gt;A table needed four columns, a self-referencing constraint, and an index. The
first migration added everything in one schema callback. That was concise, but
production could already contain any subset of those objects.&lt;/p&gt;
&lt;p&gt;The repair was longer. Each column, constraint, and index became a separate
guarded migration. The constraint waited for its column. The index waited for
both of its columns. Rollback deliberately removed nothing because some schema
objects might predate the repair.&lt;/p&gt;</description></item><item><title>Technical Debt Is a Change Tax You Can Name</title><link>https://vacua.dev/thoughts/technical-debt-is-a-change-tax-you-can-name/</link><pubDate>Sat, 01 Jun 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/technical-debt-is-a-change-tax-you-can-name/</guid><description>&lt;p&gt;A retained compatibility change introduced a contract version and a measured
fallback lookup. Related work made acquisition atomic, separated expiration
from wall-clock dates, narrowed compatibility fields, enforced authorization,
and protected unselected drafts.&lt;/p&gt;
&lt;p&gt;That history proves several rules had to become explicit. It does not prove
that the old design charged technical-debt interest. The changes might be
planned slices, newly discovered requirements, corrections, or work a
different design would also need.&lt;/p&gt;</description></item><item><title>A Rewrite Needs a Behavior Ledger</title><link>https://vacua.dev/thoughts/a-rewrite-needs-a-behavior-ledger/</link><pubDate>Wed, 01 May 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-rewrite-needs-a-behavior-ledger/</guid><description>&lt;p&gt;A desktop rewrite had screens for shipments, reports, settings, and logs. A
feature checklist could have marked those areas complete.&lt;/p&gt;
&lt;p&gt;The old application also had behavior no screen list would reveal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a failed temporary-file cleanup must not turn a submitted print into a
failure, or the next run may print the label again;&lt;/li&gt;
&lt;li&gt;the first run treats older unprinted records differently from later runs;&lt;/li&gt;
&lt;li&gt;setup unlocks when a credential setting exists, not when its value is
non-empty; and&lt;/li&gt;
&lt;li&gt;a visible report search box does nothing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of those rules protect operators. One is an awkward contract. One is
probably a dead feature. A rewrite cannot treat them all as “parity.”&lt;/p&gt;</description></item><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 Characterization Test Is Not a Product Decision</title><link>https://vacua.dev/thoughts/a-characterization-test-is-not-a-product-decision/</link><pubDate>Fri, 01 Mar 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-characterization-test-is-not-a-product-decision/</guid><description>&lt;p&gt;A label setting behaved differently in test and production. Some customers
had come to depend on the difference even though the product already let them
choose the label size they wanted.&lt;/p&gt;
&lt;p&gt;A characterization test could record both defaults. It could tell us which
callers would notice if we made them equal. It could not answer the important
question: should the difference survive?&lt;/p&gt;
&lt;p&gt;Keeping it would protect customers who had never configured the setting.
Removing it would give every environment one rule and make the customer&amp;rsquo;s
choice explicit. The same observation supported arguments for either response.
Only one response matched the product we wanted to operate.&lt;/p&gt;</description></item><item><title>A Characterization Suite Is Scoped to a Change</title><link>https://vacua.dev/thoughts/a-characterization-suite-is-scoped-to-a-change/</link><pubDate>Fri, 01 Feb 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/thoughts/a-characterization-suite-is-scoped-to-a-change/</guid><description>&lt;p&gt;A booking endpoint had more than thirty characterization cases. They covered
successful bookings, validation failures, wallet rules, locks, pending work,
email routing, and several provider response shapes.&lt;/p&gt;
&lt;p&gt;The next refactor removed one ambient HTTP dependency from the booking
services. The suite looked broad enough.&lt;/p&gt;
&lt;p&gt;It was not broad in the direction of the change.&lt;/p&gt;
&lt;p&gt;One service read the global request to persist the original payload. Replacing
that read with the existing booking data object would keep ordinary responses
green while silently storing normalized values where operators expected the
raw input. The suite already protected a test-mode short circuit. It needed a
new case for the two representations.&lt;/p&gt;</description></item><item><title>A Directory Map Is Not a Redesign Plan</title><link>https://vacua.dev/thoughts/a-directory-map-is-not-a-redesign-plan/</link><pubDate>Tue, 01 Jan 2019 00:00:00 +0000</pubDate><guid>https://vacua.dev/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. 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></channel></rss>