DEV Community

Andrey M.
Andrey M.

Posted on

A column changed type 8 days after I mapped it, and nothing told me

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`)
);
Enter fullscreen mode Exit fullscreen mode

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
    ...
}
Enter fullscreen mode Exit fullscreen mode

Eight days later, a migration I wasn't looking at changed it:

ALTER TABLE `asset`
    CHANGE `value` `value` DECIMAL(19, 2) NULL DEFAULT NULL;
Enter fullscreen mode Exit fullscreen mode

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)