Ten findings from one question: where does this service still ship its own runtime.
๐ I'm Anton - a software engineer working mostly in PHP/Symfony and Go, currently carving a live
PHP monolith into Go services. Part 2 of this series was the list of what the shared platform
library hands a service for free. This part is what happened when I turned that list into a single
question and walked the tree with it. Notes:
github.com/brilliant-almazov.
Some of this may be useful to you. Some of it you may cut differently, and I'd be glad to hear how.
These are findings from one codebase, not advice for yours.
The question
I did not go looking for bad code. I asked one question of the whole tree - 2733 Go files across
252 packages - and wrote down every place that answered yes:
Where does this service still carry code that the platform already provides?
No "good" or "bad" attached to any answer. Just a list. Ten entries came back, plus one that turned
out to be big enough to get its own section.
Two of the ten were not about style. They were about behaviour that was already wrong in
production, in a service whose tests were green.
The two that were behaviour
1. A consumer that is written and never started.
The cache-invalidation consumer exists. It has a package, it has logic, it has unit tests, and
those tests pass. What it does not have is a subscription registered in any daemon. Nothing ever
starts it. In production, invalidation does not run at all.
Nothing caught this. The package compiles - that's what a compiler checks. The unit tests exercise
its logic in isolation - that's what a unit test checks. Neither of them asks whether the component
is wired into a running process.
DAEMONS
โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโ
โ server โ โ worker โ
โ grpc handlers โ โ relay ยท retention โ
โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ cache invalidation consumer โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
compiles ยท unit tests green ยท never registered
2. Publishing with empty headers.
The service publishes messages without setting headers. The consuming side picks its codec from a
header, so with no header there is nothing to dispatch on: the platform's typed receive path is
physically inapplicable. The consumer is left calling JSON decoding by hand, which is exactly the
hand-rolled runtime the audit was looking for - except here it wasn't a preference, it was forced
by the publisher.
What these two share: the code exists, the tests are green, the behaviour is absent. Compilation
and unit tests do not check that a component is connected to anything.
The other eight
The rest of the list, one line each, no verdicts:
- The subscription sets no redelivery limit, no backoff and no dead-letter queue; a decode error is returned as an ordinary error, so the driver redelivers it - a poison loop.
- Message deduplication is a hand-written ring of 1024 ids, instead of the platform cache with TTL and metrics.
- Three hand-written caches, each a
mapplus anRWMutex: no TTL, no capacity bound, no metrics. - Bulk insert issues one
INSERTper row instead of a multi-rowVALUES. - Two different loggers, in two packages of the same service.
- One repository runs its own transaction loop:
Beginby hand,pgx.Txheld as a field, around the registry rather than through it. - The retention schedule is a constant in the code instead of a value from configuration.
- The scheduler initialises lazily through
sync.Onceover captured variables, instead of the platform's init-state store.
Every one of these has a platform package that does the same job with metrics attached. None of
them was written out of disagreement with that package. They were written because writing them was
the shortest path at the moment, and nothing later asked the question.
Forty-four copies of four shapes
Then there was the finding that didn't fit on one line. Counted on 2026-08-13, the tree contained
four shapes of "read rows from the database", copied by hand:
| Shape | Copies |
|---|---|
loop over many rows (for rows.Next() โ rows.Err() โ wrap the error) |
18 |
| fetch a single row | 6 |
COUNT(*) counter |
8 |
existence probe (a single rows.Next()) |
12 |
| total | 44 |
Of those 44, twelve differ from one another only in the text of the error wrapper.
Why this is a generic, written by copy-paste
Look at what actually varies between the copies. The result type varies. The wrapper text varies.
That is it.
A varying result type is a type parameter. A varying error wrapper is a dependency. Forty-four
copies that differ only in a type parameter and a dependency are not forty-four pieces of code -
they are one generic, typed out forty-four times.
The uncomfortable part: the correct shape was already in the tree. Reader[In, Out], with fields
executor, statement and scanner, and a single method Query(ctx, in). It existed, it worked,
and it was sitting inside one domain package - where nobody writing the forty-fifth copy would go
looking for it.
many rows loop 18 โโโ
single row 6 โโโค โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
count 8 โโโผโโโถ โ Reader[In, Out] โ
existence probe 12 โโโ โ executor โ
โ statement โ
44 copies in the tree โ scanner โ
12 differ only in the โ Query(ctx, in) โ
error wrapper text โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
A generic that lives inside a domain package is invisible. Being written is not the same as being
reachable.
The closing criterion
"Rewrite it properly" is not a criterion. It has no state in which it is finished, so it never is.
The criterion I used instead is a grep with a fixed answer:
for rows.Next(),rows.Err(),rows.Close()andQueryRow(occur in exactly one package
in the tree.
That is checkable by anyone, at any moment, without knowing the history. And it is not checked by
hand: the work is closed by a forbidding test that stays in the repository and fails the moment a
forty-fifth copy appears anywhere else.
The rule this leaves me with
There is a ladder, and only the top rung holds:
reminder โ lives for one session
rule โ works while someone is reading it
check โ works whether or not anyone remembers it
A reminder in a chat thread is gone when the thread is. A rule in a style document works as long as
people read the document, which is a decaying function. What actually holds is a check: a linter, a
forbidding test, a structure test, a hook that blocks the write.
Which turns the audit itself into a much smaller claim than it looks. An audit without a forbidding
test at the end is a one-off tidy-up. The copies come back, because the conditions that produced
them - a shortest path, no question asked - are unchanged.
What it costs
Two things, and neither is small.
The audit is manual. There is no tool that finds "runtime this service should not own", because
hand-rolled runtime does not look like a defect. It looks like ordinary working code: readable,
tested, doing its job. The signal isn't in any one file - it's that the same shape appears in
places that never talked to each other. Finding that means walking the tree with the question in
your head.
Forbidding tests add noise. Every one of them is a test that fails for reasons unrelated to
what the code does, and sometimes it catches a legitimate exception - a place where the pattern
genuinely belongs. Then you either widen the test or argue with it, and both cost time. I still
keep them, because a rule that nobody enforces costs more, later, and quietly.
Your turn
That is what one question found in one service tree, and what I chose to pay to keep the answer
from drifting back.
If you do this better than I do - a check that catches it earlier, a cheaper way to spot the
copies - I'd like to hear it. If you've been through this and it went differently, that's the more
interesting story. And if you look at it differently and think the forbidding tests are the wrong
trade, say so.
How is this solved on your side, and what broke while you were solving it?
Platform and generation - Part 3.
Next: the read core that replaced all forty-four copies - one generic instead of four shapes, and
what the conversion actually took.



Top comments (0)