The Seams

The component is not the architecture. WIRED IN DIRECTLY THREE PLACES TO CHANGE Vendor Sign-in flow Permission checks User records SEPARATED BY A SEAM ONE PLACE TO CHANGE Vendor One boundary Sign-in flow Permission checks User records Same vendor. One of these you can replace in an afternoon. PreShiftIQ

Picking the components is the easy part. The architecture is not the components. It is what your application is allowed to know about them.

Anybody can read a comparison and land on a reasonable identity provider, a reasonable host, and a reasonable payment processor. Those decisions take an afternoon and they are mostly right by default, because the leading options in each category are all competent.

The harder question took me a while to understand, and it is the most useful thing I can pass along from this part of the build. When you connect to a service, you are making a second decision that nobody talks about: how much of that service leaks into everything else. Get it wrong and you have not chosen a vendor, you have married one.

Here is what leaking looks like in practice. Your identity provider has a particular idea of what a user is, with its own field names, its own object shape, and its own quirks. If that shape spreads into your sign-in flow, and your permission checks, and your user records, and eighteen other places, then the vendor is no longer a component. It is a structural property of your application. Replacing it later is not a swap, it is a rewrite, and you will not do it, which means you have given a third party a permanent veto over your product.

The alternative is a seam. One place in the application knows what the vendor's shape is, translates it into your own, and hands that across. Everything else works with your shape and has never heard of the vendor. Now the vendor is genuinely a component, because replacing it means rewriting one boundary instead of finding every place that knew too much.

If this provider announced a price increase you would not accept, how long would it take you to leave? If the answer is a week, you have a seam. If the answer is that you would not, you do not have an architecture, you have a set of dependencies.

None of this means every connection needs a boundary around it. Some coupling is fine and some is unavoidable. What matters is knowing which is which, and drawing the line on purpose rather than discovering later that you drew one by accident.

Now the part I learned the hard way.

Components almost never fail. Connections fail constantly.

In three days I had three separate incidents that all looked like a broken identity provider and were nothing of the kind. A signing secret that matched on one side and not the other. A subscription to an event type that had silently switched itself off, which meant a message that was supposed to arrive simply never did, with no error, no log line, and nothing in the database to indicate anything had been attempted. And then a stretch of about thirty-six hours where every one of those messages failed and I did not know, because a failure that produces no signal is indistinguishable from nothing happening.

The vendor was working perfectly through all of it. The vendor is still working perfectly. What was broken was the connection, and the connection is the part I own.

That is the lesson underneath the whole topic. When you compose your application out of excellent services, you are trading component risk for connection risk, and the trade is worth making, but only if you understand what you just bought. Your components now have security teams and on-call rotations and uptime commitments. Your connections have you.

So the connections get treated as first-class parts of the system rather than plumbing. They get monitored, because a silent failure that nobody notices is worse than a loud one. Their credentials get rotated as an all-dependent-systems exercise rather than a single edit, because the failure mode of rotating one side is exactly the failure I described above. And their configuration is treated as something that can drift, because it did, three times in three days.

There is a second kind of seam that matters just as much, and it sits inside the application rather than at the edge of it.

Not all work should happen while somebody is waiting. Some things need to be done now and reported back, and some things need to be done reliably but not immediately, and confusing the two is how you get an interface that hangs for eleven seconds or a job that silently never runs. So there is a boundary between what happens in a request and what gets handed to a background worker with a queue behind it, and deciding which side of that line a piece of work sits on is an architectural decision rather than a performance tweak.

That boundary paid for itself in an unexpected way. Moving a piece of work from inside a request to the background worker surfaced a credential mismatch that had been sitting there for weeks, because the affected code had only ever executed in the service that happened to be configured correctly. The old structure hid it perfectly. The new structure exposed it in about a minute. A change I made for responsiveness turned out to be a latent-misconfiguration detector, which is not a thing I planned and is a good argument for clean boundaries generally.

Somebody will ask whether this is microservices, so I will answer it directly. It is not, and that is deliberate.

Microservices solve an organizational problem. When you have many teams who need to ship independently without coordinating every release, splitting the system along team lines is worth the cost. The cost is real: you have now turned function calls into network calls, and network calls fail, arrive twice, arrive out of order, and require you to reason about partial failure in places you previously did not have to think about at all.

I do not have many teams. I have one person. Buying distributed-systems problems to solve a coordination problem I do not have would be paying a large bill for nothing. So this is a modular application with clean internal boundaries, one background worker for asynchronous work, and managed services at the edges. The boundaries are real and enforced, which is the part that actually matters. Whether they happen to be network calls is an implementation detail that should be driven by need rather than by fashion.

There is one seam you cannot make loose, and it is worth naming because pretending otherwise is a common mistake.

The database is not swappable. People will tell you to abstract it so you could change engines later, and in twenty years of people saying that I have never once seen anybody do it. What the data layer actually needs is not portability, it is discipline: schema changes applied deliberately rather than automatically, migrations verified locally before they touch production, and a clear rule about what business logic is allowed to live in the database versus the application. Protect that boundary for clarity, not for the fantasy of switching.

If you are building something yourself, the thing I would tell you is that choosing your components will feel like the important decision and it is not. You will get those roughly right by accident. The decision that determines whether you can still move in two years is how much each of those components is allowed to know about you, and how much you are allowed to know about them. Draw that line on purpose, in one place, and write down where it is.

Selecting supply chain technology?

PreShiftIQ matches buyers to vendors on measured fit across TMS, dock scheduling, ELD, carrier vetting, and fleet management. Vendors pay for outcomes, never for influence, and the buyer match is free.

Get your free match

See how matching works at preshiftiq.com/how-it-works.

Next in the series: keeping a security posture rather than declaring one.

Next
Next

Enterprise Grade From the First Commit