One migration removed four vendor packages.
Two payment drivers did not change. Their client interfaces did not change. Their factory interfaces did not change. The concrete clients changed substantially.
Two application controllers did change. They had constructed a vendor connection directly to manage webhooks. The replacement introduced a small webhook-management role and moved the remote protocol behind it.
The same migration produced both results. Some earlier seams absorbed the change. Other paths exposed exactly where the vendor had leaked through.
That is more useful than declaring the design “clean.” A design makes a prediction about future change. The next real change tells us which parts of that prediction were right.
Design claims are predictions
An interface, adapter, factory, or repository is often defended in the present tense:
This decouples the application from the vendor.
This makes the transport replaceable.
This follows dependency inversion.Those statements are too broad to verify.
A narrower claim can be tested:
If the HTTP library changes, payment decisions will not change.
If the vendor SDK disappears, the driver will retain its client contract.
If webhook framing changes, the connect workflow will not learn the wire
protocol.The design is valuable to the extent that the predicted change lands where the system said it would.
This does not mean every good design needs a replacement in production before it can be trusted. It means the strongest evidence arrives when a real change presses on the seam.
Define the change before scoring the seam
“We replaced the SDK” can hide several independent changes:
- remove an unused package;
- replace outbound request construction;
- replace authentication and signature code;
- decode callbacks without the SDK;
- translate provider errors;
- manage a remote resource lifecycle; and
- preserve application behavior while all of that moves.
The retained migration did all of these across several integrations. Scoring it as one change would make the boundary analysis vague.
Start with a change inventory:
| Change pressure | Expected owner |
|---|---|
| request URL, method, and body | transport adapter |
| authentication placement | transport adapter |
| callback extraction and verification | protocol boundary |
| charge and refund decisions | payment driver |
| webhook registration policy | application capability |
| dependency construction | factory or composition root |
Now the diff can answer a meaningful question: did each change reach the owner we expected?
Unchanged callers are evidence
For two payment integrations, the application already spoke through client and factory interfaces. The replacement changed concrete client internals and introduced explicit request objects, but it left the payment drivers and their interfaces alone.
That is direct evidence for a bounded claim:
transport replacement
changed adapter
changed request construction
preserved driver
preserved client role
preserved factory roleThe value is not the interface count. It is the absence of transport-driven edits in code which owns payment decisions.
Tests add weight to that observation when they exercise the unchanged caller against its declared role and exercise the new adapter separately. A quiet diff without behavioral coverage can mean the seam worked. It can also mean the change was incomplete.
An unchanged file is evidence only after the replacement path and caller behavior both run.
Changed callers reveal the leak map
The commerce integration had client and factory interfaces too. That did not protect every workflow.
Its connect and disconnect controllers stepped around those seams. They constructed the vendor connection, selected a dynamic event-listener resource, used remote event and field names, and caught package exceptions.
When the SDK left, those controllers had to change. The diff revealed the actual dependency graph:
connect workflow
knows vendor connection
knows remote resource grammar
knows remote event vocabulary
knows package failuresThe replacement added a two-operation webhook-management role. This did not make the earlier design a failure in every respect. The existing client seam still protected other commerce operations. It showed that the seam stopped short of one lifecycle.
That distinction matters. “The adapter failed” throws away useful evidence. “The adapter protected order operations but not webhook lifecycle” tells us where the next boundary belongs.
A stable interface can preserve the wrong thing
The payment seams survived, but their shapes were not provider-free.
One interface returned a framework HTTP response. Another returned mixed and
arrays. Callback parsing produced arrays. One factory retained a credential
parameter which the replacement client no longer needed because changing the
factory contract would have disturbed callers.
The replacement therefore preserved both useful roles and old leakage:
useful stability
driver operations remain unchanged
construction remains substitutable
transport internals move outward
preserved cost
caller can still see transport response semantics
arrays still carry provider-shaped data
obsolete construction input remains in the roleCompatibility is not the same as quality. A stable interface can make a bad type durable.
The correct conclusion is not that the seam should have been removed. It is that replacement evidence has two axes:
- how much unrelated caller change the seam prevented; and
- how much provider or transport meaning the seam still allowed through.
A useful seam can score well on the first and poorly on the second.
Count change distribution, not changed files
File counts are easy to collect and easy to misuse.
A replacement may change forty adapter and test files because the remote API is large. That can still be well-contained. Another replacement may change only four files, all of them central business workflows. The smaller diff can carry the larger architectural cost.
Classify changed files by responsibility:
expected
dependency manifest
connector
request objects
protocol parser
adapter tests
boundary repair
new application capability
composition binding
caller changed to receive the capability
unexpected propagation
business decision changed for transport reasons
domain result gained vendor fields
unrelated workflow learned new authenticationThe retained migration contained expected adapter work and a visible boundary repair. It did not require the two payment drivers to learn the replacement transport. That distribution says more than the aggregate diff size.
Tests show whether the seam has an owner
The strongest replacement tests sit on both sides of the boundary.
Caller tests should prove:
- the same charge and refund decisions still hold;
- application workflows persist or remove local webhook state correctly; and
- provider failures become outcomes the caller understands.
Adapter tests should prove:
- request method, path, body, and authentication;
- callback verification and malformed input;
- response extraction; and
- remote failure translation.
Two previously skipped controller tests became practical after the webhook capability could be substituted. That is evidence of a boundary becoming testable. Mocked transport tests still do not prove that a live provider accepts the request.
Keep that limit visible. A seam can make local behavior easy to test while the remote contract remains wrong.
Keep a next-change scorecard
The public reproduction applies one synthetic SDK replacement to four consumers:
- two callers behind existing roles;
- one caller with direct vendor grammar; and
- one caller behind a stable but provider-shaped result.
It produces a scorecard rather than a pass/fail verdict:
callers unchanged
callers changed for boundary repair
transport details contained
provider meanings still leaked
new tests made possible
live-provider behavior unprovedThe fixture does not reproduce the private migration or its framework. It models the evaluation method and asserts that an unchanged caller can coexist with a preserved semantic leak.
Use the same scorecard after a real change:
- state what the old design predicted;
- inventory the actual change pressures;
- classify changed callers and adapters;
- record which seams held;
- record which leaks forced propagation;
- record which old leaks remained stable; and
- decide whether the newly observed pressure deserves another boundary.
This turns “the architecture helped” into an explanation someone else can challenge.
Let the change revise the principle
Before the migration, “depend on interfaces” might have sounded sufficient. Afterward, the evidence was more precise:
- an application-facing role can absorb a transport replacement;
- a factory can preserve runtime-specific construction;
- an interface does not help a workflow which bypasses it;
- stable arrays and response objects can preserve provider coupling; and
- a new capability should follow an observed caller need, not a desire for more interfaces.
That revised principle is more valuable because it carries its failure conditions.
Design is not proven when the diagram looks orderly. It becomes useful when a change arrives, most of the edits land where predicted, and the exceptions tell us exactly what the earlier model missed.