back

by iamflimflam1·1mo ago·view on hn ↗
Feels like everyone has to go on the journey.

ORMs are bad - I’ll just use SQL.

Hmm - I need to map these results onto objects I can use.

Hmm - wouldn’t it be great if the object tracked changes and could save itself.

I need related/child objects - wouldn’t it be great if I could auto fetch them. …

9 comments
I think that journey only feels inevitable if you start from the assumption that the application object model is the centre of the system. An alternative journey:

Hmm – I should model the data according to the domain, not according to the shape my application objects happen to want.

Hmm – maybe “related objects” are not things to auto-fetch, but relationships the database engine is already built to handle.

Hmm – now that my schema matches my domain, complex problems can be solved with a few lines of SQL, saving me hundreds of lines of application code.

Hmm – in fact, now I realise that many important operations can be performed without round-tripping the data through application code at all, saving me thousands of lines of application code.

Using an ORM should not exempt you from designing your schema around the domain. If anyone thinks it's the case they're either using the ORM wrong, or using the wrong ORM, or perhaps creating the wrong ORM.

Similarly, designing your schema to match the domain does not necessarily grant you the productivity boons of an ORM.

Having (ab)used Postgres with and without ORM, I've never had a situation where the latter imposed any kind of design decisions on the schema. They're orthogonal concerns. Itself, the choice of using an ORM tends to be motivated by experience with certain requirements in the business logic. I love SQL, but when having to deal with API resources and their various representations, marshaling, validation, options, etc, it's difficult -- and to say the least, impractical -- to stay principled to the "no-ORM" and "raw-SQL" mottos.

ORMs and schema design are not fully orthogonal. A database schema and an ORM model can be conceptually separated, but in practice they influence each other. And even if you are vigilant, knowing that you'll use an ORM will cause you to obsess about things which satisfy the ORM at the expense of good modelling.

TLDR: Tooling pressure is a design pressure.

None of your points remove the need to map db values to objects and to fetch related objects.
If you just want to store and retrieve objects, and then store and retrieve "related" objects, what you want is an object store, not a relational database. You can use an ORM to shoehorn it into a relational database engine, but don't fool yourself into thinking that's the same thing as using a relational database engine properly.

Obsessively cramming tabular data into objects is often unnecessary, and it bloats the code downstream of the database query. It then encourages the bad habit of performing data manipulation in code rather than directly in the database.

"Fetch related objects" is a code smell. If any related data was needed, your original query should have already fetched it.

Problem is that doesn't work nicely in a one-to-many or many-to-many relationship - fetching it in the original query means deduplicating in the application code, or not fetching it and getting related rows afterwards. And that's one of the things ORMs are really good at.
For a video game, simulation, CAD program or other stateful program, loading an object graph can be natural. In that context, an object store, document database, serialized state, or ORM-backed persistence layer may be a reasonable fit. This isn't database design, it's application design.

But in data management applications, the job is to derive specific information from stored facts. For that, SQL is not an implementation detail behind objects; it is the main abstraction. The whole point is to ask for the shape of data your application actually needs, not to arbitrarily hydrate objects and reinvent a bespoke querying engine on the client side. I can barely even remember the number of times that I've ripped an ORM out of a system because the code to interact with hydrated objects had devolved into a single-purpose database engine, a sprawling mess of code, seemingly well organised into objects, but ultimately wasteful.

Often, de-duplicating in code is a perfectly fine solution, and significantly more performant than multiple round-trips. A join that repeats parent columns is not a flaw in SQL. It is only a problem if the application insists on rebuilding a nested object graph instead of asking for the shape of data it actually needs.

If the data returned by the query does not match what you are presenting, the answer should not be to fetch a pile of related objects and interrogate them in memory. It is to use more SQL to further digest the data so that the result set more closely matches what you intend to present to the user.

I've went on a similar journey and did just end up going to back SQL.

Mapping database rows to domain objects really isn't that painful and you only do it once. So not a big deal. LLMs actually make this a non-issue now.

I actually realised I like the separation of domain objects and the data layer. It just makes things easier to think about for me. And it means my data layer is completely abstracted from the domain. Makes it easier to implement different storage/caching strategies.

And I also realised, if your hot path is needing to get related/child objects then you should probably just write an optimised query as a prepared statement or a stored procedure. It's rare that you actually have that many different ways you want to access the data.

They are really useful for speed of development though if something is completely greenfield and you don't know the full picture of how data is going to be accessed.

It's not one or the other, it's both. Sqlalchemy ORM is my favorite, followed sqlc (golang), because they are both there for you for your highs (select * from table order by created at) and your lows ([an inner join followed by 4 left joins with an aggregation function])
If you're working at any decent scale, the journey is the opposite IMO.
You stopped right before the best part. When they decide to create their own ill designed, badly tested, and undocumented mapper.
> Hmm - I need to map these results onto objects I can use.

What sql client is going to hand you raw text?

> Hmm - wouldn’t it be great if the object tracked changes and could save itself.

Lost me there.

> > Hmm - I need to map these results onto objects I can use.

> What sql client is going to hand you raw text?

In python's standard database interface you access columns by index, not name. I figured that's what they're referring to.

Entities
ultimately, there is no silver shortcut - you just have to write the damn code
Yeah, exactly. I think the best approach is always to know SQL and know the ORM.

Most of the time you’ll be able to simply use the ORM, but every so often you’ll inevitably come up against a situation where a custom query gets the job done better, and you’ll still get the benefits of deserialising to objects that the ORM offers.

> you’ll inevitably come up against a situation where a custom query gets the job done better

In my experience, these are typically best turned into views (or materialized views), because they represent some fundamental relationship or property within the data that’s useful to be able to quickly reference or query directly against. KPI aggregates, for example.

As long as you restrict yourself to an ORM-compatible schema, you are restricting the power of SQL available to you. Learning SQL properly means learning to model your data correctly, and this usually makes ORMs a non-starter.

Without an ORM you have to write a bit more boilerplate code to interact with the database. But by taking advantage of the power of your database engine, you could potentially avoid writing huge amounts of data manipulation logic. In my experience, an ORM is more of a code amplifier than a code simplifier.

All of this depends on the problems you’re solving though. There is no one size fits all approach to database development.
Generally speaking if an ORM is a good fit, the thing you're doing probably isn't database development, it's application state management disguised as database development.
ORM is compatible with any schema, but perhaps you are thinking of something like the active record pattern?
With a few extra lines and mapping objects to classes this can be done.

To have all this ease of use you give up so much in performance.

Most apps and companies never get to the point where performance matters that’s why we have ORMs.

> you give up so much in performance.

Not really. ORMs (memory) and databases (disk) are distant by multiple orders of magnitude performance wise. Skipping the ORM to shave off some cycles is akin to haggling over a few pennies on your thousand dollars bill.

SQLAlchemy vs hand rolled SQL and mapping the results it’s not even close. The overhead if you need it cuz you’re sub scale so be it.
its a good abstraction if you build it yourself.