GoLazy Blog
Why GoLazy Has No ORM
LLMs made SQL cheaper to write, but they did not make business modeling less important.
For years, one of the practical arguments for an ORM was that nobody wanted to write the same database plumbing again.
Define a model. Declare a few associations. Ask it a question in the host language. Let the framework turn that into SQL. The abstraction was not only elegant; it removed work that developers were tired of doing by hand.
Then LLMs became extremely good at writing SQL.
Not flawless. Not magically aware of your indexes, production cardinality, locking behavior, or the query plan waiting to ruin Friday evening. But very good at producing the first useful query, the scanning code, the migration, and the tests around it.
The economics changed.
Writing SQL is cheaper now. Understanding the business is not.
The valuable part was never only query generation
It would be stupid to conclude that Active Record no longer matters because a model can write a JOIN.
The valuable part of a good Rails model is not merely that it saves SQL keystrokes. It gives the business a vocabulary. A model can gather state, validation, associations, and behavior into something a developer can read and reason about.
Open a good model and you can learn what the application believes an account, subscription, invoice, or publication actually is. That remains valuable whether the query was written by a person, generated by an ORM, or suggested by an LLM.
The problem is not modeling.
The problem is making the persistence abstraction the mandatory center of the business architecture.
Abstraction has a maintenance cost
An ORM lets developers avoid SQL until it does not.
Eventually a query matters enough that somebody must understand what was generated. A relation performs poorly. Preloading changes the result shape. A callback runs at an unexpected moment. A transaction boundary is not where the business operation needs it. A feature crosses several models and an external API, so the interesting behavior moves into a service object anyway.
At that point the application pays for two things:
- the database behavior itself;
- the abstraction used to avoid seeing the database behavior.
That tradeoff can still be worth it. Rails proves that it often is. GoLazy simply refuses to make it a framework requirement.
Go does not offer Ruby's dynamic Active Record experience naturally. There are capable Go ORMs and code generators, but each makes different choices about schema ownership, generated types, query APIs, migrations, relationships, and runtime behavior. Choosing one would force every GoLazy application to accept a large architectural decision that has nothing to do with rendering a web page.
So GoLazy does not choose one.
Let services own persistence
In a GoLazy application, services own business behavior and data access.
A service may use SQLC-generated queries:
type Service struct {
queries *store.Queries
}
func (s *Service) Publish(ctx context.Context, id int64) (Post, error) {
// Check the business rules, persist the change, and return a domain result.
}
Another service may use an ORM. Another may call an API. The controller does not care:
func (c *PostsController) Publish(id int64) error {
post, err := c.posts.Publish(c.Request().Context(), id)
if err != nil {
return err
}
return c.RedirectTo("/posts/"+post.Slug, http.StatusSeeOther)
}
The exact APIs will vary, but the ownership does not. The controller translates the request. The service performs the business operation. Persistence stays behind that boundary.
GoLazy can initialize a database pool, run embedded migrations, expose service health, and provide durable backends for jobs or storage. Those are application mechanics. It does not decide that your posts table is your business architecture.
LLMs remove toil, not judgment
LLMs strengthen this choice because they are useful exactly where explicit database code used to feel disproportionately expensive.
They can draft:
- schema migrations;
- SQL queries;
- SQLC definitions and Go call sites;
- scanning and mapping code;
- fixtures and integration tests;
- boring repository methods when a repository is actually useful.
That makes direct, inspectable code more affordable.
It does not make that code correct by divine intervention. The developer still owns constraints, transactions, query plans, indexes, authorization boundaries, migrations, and production behavior. Generated SQL must be reviewed like generated Go.
More importantly, an LLM cannot decide what the business should mean. It can reproduce patterns. It can suggest names. It can confidently put a rule in the wrong layer with excellent formatting.
The scarce skill is no longer typing the query. The scarce skill is knowing which operation the application should expose, which invariants it must preserve, and where that responsibility belongs.
That is service design.
No ORM is not an ORM ban
GoLazy does not ship an ORM. It does not prohibit one either.
If Ent gives your application the right schema workflow, use Ent. If GORM makes the team productive, use GORM. If SQLC plus PostgreSQL fits, use that. If SQLite and handwritten queries are enough, congratulations on avoiding a meeting.
The framework does not need to win that argument.
Its job is to give the application a stable web shape around whichever persistence choice the business requires. The service boundary lets that choice remain local instead of turning it into the identity of the entire framework.
LLMs made SQL cheaper. They did not make domain modeling obsolete. If anything, they made the distinction harder to ignore.
Code is becoming abundant. Knowing what the code is supposed to do is not.