I was rewriting a Java/Spring backend to Go. The database wasn't going anywhere — it
already existed, with real data in it, and eighteen tables' worth of history behind it.
The plan was simple: keep the schema, replace the service layer.
Early on, asset looked like this:
CREATE TABLE `asset` (
`id` bigint NOT NULL AUTO_INCREMENT,
...
`value` int NOT NULL,
PRIMARY KEY (`id`)
);
I mapped it to a Go struct by hand, the way you do when you're moving fast and the table
is right there on the screen:
type Asset struct {
ID int64
Value int64
...
}
Eight days later, a migration I wasn't looking at changed it:
ALTER TABLE `asset`
CHANGE `value` `value` DECIMAL(19, 2) NULL DEFAULT NULL;
int NOT NULL became decimal(19,2) NULL. Two changes at once, and my hand-written
struct still said int64, no pointer, no way to represent NULL. Nothing failed loudly —
GORM will happily scan a decimal into an int64 and either round it or blow up on a NULL,
depending on the day. It's the kind of bug that shows up as "why is this value off by a
few cents" three weeks later, not as a compile error.
That's when it clicked: I don't need a unit test for "does my Go struct still match the
asset table." I need something that reads the table and tells me if it doesn't.
So instead of writing the struct by hand, I pointed a small code generator I'd built for
exactly this — reads live schema over JDBC, spits out Go structs — at the same database,
and diffed the output against what I had. It caught value immediately: wrong type,
wrong nullability, no compile error to warn me.
None of this is exotic. If you're doing DB-first Go with GORM, this is just what happens
when the schema evolves and your structs are maintained by hand, by a human, on a Tuesday.
The fix isn't "write more tests" — it's "stop hand-maintaining the mapping between a table
and a struct, and regenerate it from the table instead." A unit test only catches what you
thought to test. A schema read catches what actually changed.
I ended up formalizing this into SQL DAL Maker
— it reads live JDBC metadata and generates the DAO/model layer for Go (and a few other
languages) from it, so the struct-vs-schema gap can't quietly reopen every time someone
runs a migration. But the generator isn't really the point of this post. The point is:
if your models are hand-typed and your schema isn't frozen, you already have this bug
somewhere. You just haven't hit it yet.
Top comments (0)