Hacker News new | ask | show | jobs
by tengbretson 27 days ago
I don't disagree with any of the major gripes people have with orms and I find SQL to be much cleaner in a lot of circumstances.

That being said, if orms didn't force you to explicitly define your domain models about 60% of developers would simply never do it. And you would see differently structured, ad-hoc interfaces defined all over the code base completely entangled with whatever action they are trying to perform.

ORMs being a forcing function for domain modeling is enough benefit for me that it outweighs all of their obvious limitations.

10 comments

>ORMs being a forcing function for domain modeling is enough benefit for me that it outweighs all of their obvious limitations.

That was a surprising take!

I know only a few ORM's but it seems they end up just adding another layer of DTO objects that are entirely separate from the domain classes anyway. So best case the ORM is just a detour for a good domain model. Worst case it creates a weird database-contaminated domain model that's hellish to maintain.

So I would't say ORMs force domain modeling, or even help. Are you perhaps thinking of a particular stack where the ORM is just one part of it?

Why is your database so different from your domain?
>Why is your database so different from your domain?

Usually it's due to one of these:

- The domain deals with a lot of things that are not in the database.

- The domain is one of many and deals with just a fraction of what is in the database.

- The domain deals with things stored in several databases.

- The database was designed in the 90s and the domain is new.

- It's not my database so I can't change it.

(Even for greenfield systems I don't think it's generally desirable that the database matches the domain model.)

It's not that your domain is different, it sounds more like you don't know how to use ORMs. ORMs don't have to manage migrations, they don't have to even write into the database. When dealing with a bad database design, it can be a legitimate tactic to use ORMs in read-only mode and have writes still as hand-rolled SQL. You can do database-first ORMs, as well as code-first, where the database design is king, not the POCO.

> The domain deals with a lot of things that are not in the database.

You can have non-serialized properties. You can even can over-ride serialization/de-serialization of individual properties

> The domain is one of many and deals with just a fraction of what is in the database

You can use different ORMs for different parts of your domain, you could even wrap multiple ORMs in a wrapper repo pattern if you want

> The domain deals with things stored in several databases

As above.

> The database was designed in the 90s and the domain is new

Tons of solutions for this, one easy one is using SQL Views, just ask Claude. The weird thing here is that I've now dealt with this IRL like 5 times and came to the opposite conclusion of you. I found wrapping a bad DB design with an ORM a great first step in fixing it, as the ORM effectively acts as an easy strangler pattern.

> It's not my database so I can't change it

You can still use ORMs, ORMs don't have to manage migrations. Though I feel sorry for you working somewhere you still have a DB guy gatekeeping the database design in 2026.

The point is, every one of your objections are pretty trivially solvable with many mature ORMs, because everyone else had the same problems two decades ago and instead of throwing up their hands and hand-rolling their SQL, the ORM tooling was improved.

>It's not that your domain is different

You have mixed the posts you are replying to - the domain being different from the database is stipulated here.

I was giving examples of how this typically happens, and the reasons are entirely independent of whether or not an ORM is being used.

I am fully aware that you can handle any mess using an ORM as well, which is why I was surprised at the original claim that ORM's force proper domain models. I haven't observed that so I was genuinely curious.

Separately from that I have to say your suggestions of things to do to force an ORM into the situation are bad ideas. The complexity of custom serialization, various mapping hooks or attributes to bless individual properties will lead to pain and misery down the line.

Just accept the extra layer of DTO's. They're a detour over pure SQL but are at least easy to maintain and hold no surprises. They say there's a special place in hell for people who write SQL triggers and I think people who override ORM serializers are welcome there. ;-)

No thanks.

DTOs are one of the big code smells of a code base that does little but will be full of boilerplate. As soon as you see an automapper or a folder of DTOs you know you're in for some serious pain.

On the plus side you also know you can reduce the codebase by about 75%.

That doesn't sound at all like any ORM I've ever used. I've struggled in the past because The ones I've used are actively hostile to laying out data in the database in a way not proscribed by the ORMs philosophy. Heck of the ORMs I've used, one didn't support parameterized joins and the other didn't support joins at all.

---

It's not usually a DB guy gatekeeping, it's that multiple apps use the same database so layout changes are costly.

To be fair, there are a lot of lousy ORMs. Research and test well before adopting.
Except for the "multiple ORMs" part which is a level above it, it applies to the only one I've used extensively: Django for python. It has standard defaults, but just about everything overridable, and because models are python objects you can add methods or properties for extra data. There's even ways to define your own field types (the "serialization/de-serialization of individual properties"), which a decade ago people were using to provide json fields through libraries long before it was officially supported.

...and Django was like this 15 years ago when I first started using it. The core design hasn't changed, it just sounds like most other ORMs don't really know what they're doing.

Most commonly ime the application domain may only be some small fraction of the database which is designed/optimised for a much broader dataset.
I doubt domain logic would ever be in 2NF. My domain logic certainly doesn’t have pivot tables of IDs for join lookup
> I know only a few ORM's but it seems they end up just adding another layer of DTO objects that are entirely separate from the domain classes anyway.

Entity Framework in particular has come a long way in this regard. Particularly owned & complex entities, value converters, etc.

https://learn.microsoft.com/en-us/ef/core/modeling/

> Worst case it creates a weird database-contaminated domain model that's hellish to maintain.

CQRS is good for this because it forces you into using a different write and read model. My write models are domain objects and my read models are DTOs that feed the UI and via projection I can shape them without issue.

Mh, in NHibernate when properly modelled, your Domain classes usually match the database/tables, so there is no need to have additional DTOs
Additionally I think the migration management that most ORMs support are also a good thing. Defined and type-safe forward and backward strategies are helpful in most cases, especially if you'd like to support more than one DBMS.

I personally think that ORMs are good for management and simple CRUD cases, QueryBuilders are good for managing more complex queries while still being secure / type-safe and for everything else a thin database abstraction layer for native SQL queries with parameters / prepared statements is still required especially for performance use cases.

> ORMs are good for management and simple CRUD cases

I for one think that "simple CRUD cases" is bullshit, those applications don't exist. In practice, System-of-Records systems are rare. (and should be, their value are inversely proportional of how many of those you have in your overall system).

Because if it was "just simple CRUD", one would use the database directly? Databases are already capable of handling CRUD and much more with way less implementation bugs.

Even assuming your application "is a system-of-record", how is it giving any more value that directly using a ready-made solution like Oracle REST Data Services, or PostgREST?

No. If you have a simple line-of-business app, writing Django/Rails models is FAR easier than the equivalent SQL.

Even if you think that maintaining your domain model is easier in SQL (it’s not, for most full-stack engineers), the extra capabilities you get from an ActiveRecord framework such as full-stack admin pages, free migrations, etc. win overall.

I can believe that the gap is closing with the “api for your Postgres” frameworks but really, try reaching your frontend developers sql and see if they have a better time than learning Django/Rails.

I wrote my own ORM for exactly this reason. It's far from enterprise grade but it solves for 1) the domain model needs to stay clean and properly normalised and 2) that's not a job that can be distributed across the whole team.

One lesson I've carried for years is that most of the time the client needs denormalised views on the data model. That's the boundary; the server has the clean domain model, and the client works with views on that model. Isn't that exactly what an ORM is for?

I built mine in Dart because I want the server and client to share DTOs. Then I built a visualizer for clientside devs to be able to explore entity relationships (DDD style) and generate a JSON contract. The end result is no REST back and forth, no GraphQL complexity, just everyone in the team focusing on what they're good at.

I think the theme that ORMs are easy to start, but you pay for it with the edge cases, so good devs end up back at SQL does not apply when you're thinking about how to build a platform. Everyone has their strengths and weaknesses. Aligning the team on playing to their strengths was my goal when I reached for yet another ORM as the solution.

> extra capabilities you get from an ActiveRecord framework such as full-stack admin pages

The naive "here's a row-level view of the database records" that things like ActiveRecord/ActiveAdmin give you by default are entirely inappropriate for any line of business administrative interaction. Line of business admin sites should be workflow based and focused on surfacing specific information needed for processes outside of the admin site itself. Non-developer staff should not be expected to interpret the state of rows and relationships among the tables.

As my career progresses, I'm starting to understand just how many developers have trouble comprehending invariants and how they affect system design. If you do not comprehend invariants, then every system is CRUD.

The specific danger of CRUD is that all operations are expressible in it. If your system is CRUD, everything goes. A developer who doesn't understand the system's design might be inclined to assume an application is "just CRUD" and add all sorts of misfeatures to it that violate otherwise constrained states. They will turn the application into CRUD.

All it takes for an application to go from carefully modeled to CRUD is for people to believe it already was just CRUD.

This comment reads crazy poorly.

Like, if the simple insert, read, update and delete SQL queries are forbidden then what do you guys do all day?

Are you really doing inserts exclusively based on the data of another table? You never take user input from a website? You never need to just get a list of data according to a query with some filtering?

Honestly I'm not buying it, since the opposite would basically require you to write Hasura style monster queries for pgsql all day.

An invariant is something that must always be true. The most basic example of this is a not-null foreign key, where a value in one table refers to a row in another table and that row in the other table must always exist.

> Are you really doing inserts exclusively based on the data of another table? You never take user input from a website? You never need to just get a list of data according to a query with some filtering?

None of these go against what GP said in any way.

Agreed. Simple CRUD is something that only shows up in the beginning of the project, everyone was told to use ORM for that purpose, business grow, and you had awkward requirements that require complex ORM features which might exist but requires deep dive into ORM library's corner case, or just straight not possible and makes you bang your head wishing you'd write SQL instead where it would have been obvious what to write.

The only good thing about ORM is the type safety, but I find rust's sqlx or java's jooq to be hitting the sweet spot.

But SQL is low level, eg. you can't dynamically pass in filters and construct statements without knowing what the query will be ahead of time. ORMs have query builders that allow you do dynamically construct SQL statement based on parameters, they allow avoiding N+1 queries by doing joins in memory and much more. It's just not possible with vanilla SQL unless you concatenate strings or have multiple versions of the same query for every situation. A good ORM is an abstraction layer above that gives you more powerful tools, it's like comparing high level OOP code vs machine code.
Query builders are a doddle to write, extremely trivial to debug, and generally far easier overall than having to shoehorn data into a structure that your ORM likes.

The problem is not that ORMs fail to expose every feature of a particular SQL database. The problem is that they encourage you to model your data in a way that is convenient for the ORM, rather than in a way that is correct for the domain.

Any sufficiently powerful ORM eventually has to provide escape hatches into SQL. At that point, the abstraction has failed: the ORM is no longer helping you understand the database, it is getting out of the way so you can use the database properly.

An ORM is a straitjacket. It pushes you toward sub-optimal structures, and those structures deny you access to the most powerful aspects of SQL: relational modelling, constraints, joins, aggregation, views, transactions, and set-based operations.

It seems like you are throwing away the baby with the bathwater. I don't think providing 90% of the structure you need is a failed abstraction. And it just doesn't follow that it is pushing you towards sub-optimal structures, not sure where this conclusion comes from. All ORMs I've seen have ways to describe relations between models, even polymorphic types, aggregates, eager loading (to avoid N+1) etc.
If the ORM is capable of validation or integrates with such a component, i personally think that it integrates well for these parts of an APP, where simple datamanagement is required... E.g. adding, editing and deleting DB records, that need forms and validation.
Developing apps in SwiftUI it’s extremely useful to have SwiftData, an ORM that can act like a system of records. For my own use case this is basically metadata for large scale datasets in Parquet or similar.

That said it’s still my most frequent cause of crashes, however I think mostly it’s just because this is simply a hard problem that SQLite just isn’t cut out for (although it did take Apple until macOS 27 to supply a codable decorator grrr).

Ideally databases could evolve to fit OR mapping more closely, which incidentally is what Arrow and Parquet have done to an extent.

I actually spent a majority of the last ten years on simple CRUD cases.

I mean think about it, creating a single row is CRUD, retrieving a row by ID is crud. Retrieving all the rows that belong to a user with pagination is crud, updating a specific row by ID is CRUD, deleting a specific row by ID is CRUD.

You have a settings page? CRUD

User profile? CRUD

Application form with more than 120 input fields, complex tables, split across multiple pages? CRUD

Heck I have a version of that where you get to nest multiple sub application forms into a single application with absurd amounts of nesting and it is still CRUD.

Most cases that aren't CRUD tend to be niche cases.

I mean think about it. An extended search feature that has hundreds of options is still CRUD and tends to work much better with an ORM since the query builder dynamically builds the SQL query for you instead of messing around with a static SQL query with a massive amount of feature flags.

The cases where you don't have CRUD are the rare cases. Things like reporting, batch jobs that process multiple rows at once or reconciliation that tries to find the differences between two databases.

Maybe it's not clear, but the arbitrarily complex application logic obviously is not written in SQL so even if the application is more complex than a straw man CRUD example doesn't mean that the database sees something more complicated than row creation, retrieval, updating and deletion.

I understand you mean “data” model instead? Perhaps for simple cruds, there’s no much point in differentiating between the data model and the domain model. For more complex scenarios, having orm concerns leak into the domain model is not nice
This is exactly what happens in a typical Elixir project even though Ecto is a query builder and not an ORM. People define their domain entities as database tables. The result is, from my latest project, you have user and organisation memberships which are a list of membership records. This is carried throughout the application while it should be a hash map of organisation IDs and membership data, so you can check if a user is a member of an organisation in constant time. Of course keeping ourselves coupled to the database representation is easier than defining a view, for example, which takes care of presenting the data in a useful form for the application.
That’s a problem with ecto and not ORMs. A good ORM will be able to do that hashmap mapping.
It’s a problem of philosophy, not tooling.
This is one of the major drawbacks to ORMs imo (though it’s not necessarily the ORMs fault). People think domain and data models are the same but they most definitely are not.
SQL is pretty shitty language to write modular, reusable and easy to read code.
ORMs often end up defining their own domain-specific language which is neither SQL nor the language itself, so they don't really solve that problem except in the simplest cases.
With the big exception of Entity Framework which relies on LINQ.
It's easy to read SQL code as long as the person writing the query doesn't resort to "hack" the planner. Now to make that reusable....That's another beast and most often will defeat the purpose of writing pure SQL in the first place, let's put a OR there, let's call several queries, let's screw the data model.
It solves a hard problem. For example, it completely insulates the sender from the fact that his transaction is just one among many others.
No, database servers solve that problem. That the unnecessarily COBOL-like SQL ended up being the primary interface to them is simply an unfortunate accident of history.
There have to be some limitations on the language for this to work. For example, there must be no way to specify a neverending loop or to lock something and forget to unlock, an so on.
You shouldn’t use ORM entities as domain models. The domain should not depend on anything from the integration layer (db entities, REST request/ response, etc).

Ideally models are generated from SQL schemas, which you map to domain models.

I think you hit the nail on the head.

I’m thinking about what Rails would look like without activemodel and activerecord. Or even just without activerecord, where we had to write the same sql every time we wrote a model but introduce the opportunity for a dev to screw it up. Imagine starting on a legacy code base and all the models had subtle differences in how they query the db. They don’t have the established conventions around _id fields, polymorphism, the nice bits around joins, and instead you have to discover bugs where you did a join but the two models each have a field called “description”…

Wouldn't you consider defining the schema doing the domain modeling?

I think ORMs do too much. I want to control the querying, or, more precisely, I want to control the SQL that goes to the planner. The good ones largely do allow for this, but I can't think of one that has innate support for vendor-specific features.

What I do appreciate is that they handle the boilerplate like managing connections, preparing statements, setting parameter values, and mapping database types back to client types.

> Wouldn't you consider defining the schema doing the domain modeling?

No, because if the schema is the only reference for data models, developers on any sufficiently large team will come up with extremely widely varied queries to access equivalent information. Those are more likely to be incorrect (someone with domain expertise on one set of tables might miss that authoritative data needs to be joined/queried from elsewhere), harder to update when schemas change (more client code changes to alter and test), and more likely to miss performant techniques to query data.

Those can all be addressed with disciplined use of views or common utility SQL snippets or functions, but ORMs also get you to that point without requiring as much ongoing discipline, care, and feeding.

> Those can all be addressed with disciplined use of views...

Totally agree. Views as a data API is the best way to take advantage of the facilities that the database itself offers and guarantees enforces consistency across disparate clients.

> developers on any sufficiently large team will come up with extremely widely varied queries to access equivalent information.

Ah yes, the famous database integration anti-pattern.

> but ORMs also get you to that point without requiring as much ongoing discipline, care, and feeding.

[citation needed]

The fact that you have being practising "database integration" won't suddenly disappear just because you used a ORM. In fact I expect even worse database integration from your average ORM user, as people that uses ORM blindly often don't care (to their own detriment) about "silly issues" like data provenance or persistence mechanical sympathy.

At some point I expect the DBAs of such database integration nightmares will have to start handling stuff like column-level security and row-level security to prevent naive users from shooting themselves in the foot.

> Wouldn't you consider defining the schema doing the domain modeling?

To an extent, yes.

But to the extent that a so-called impedance mismatch exists, this is going to put your domain model on the faraway/difficult side of that impedance mismatch.

And will result in your domain model existing in an (on average) less expressive language which is more difficult to test.

> if orms didn't force you to explicitly define your domain models about 60% of developers would simply never do it

This was never the experience I had. If anything, people tend to plan too much.

I'd rather take a mess of ad-hoc interfaces. Forcing people to do domain modeling does not go well.
Pretending that domain modelling is optional does not go well.
Sure, but it's better to do no domain modelling than to pretend doing it.
“Doing no domain modelling” is not really an option. It just means the domain model emerges accidentally from ad-hoc interfaces, conditionals, database fields, validation rules, and UI assumptions. Asking an LLM to help with domain modelling isn't ideal, but it's strictly superior to having your model designed by accident, informed primarily by the initial rough draft of your application code.
Too often, the avoidance of learning SQL creates more work than learning SQL.

One example is starting with NOSQL and proceeding to learn how to make it into a relational database.