News for hackers. Stuff that matters.
Re:ORMs Are Backwards by DevKoala on Tuesday September 03, 2019 @08:19PM (#20872747)
It looks like the author is generalizing shortcoming in SQLAlchemy as faults with the concept of ORMs. ActiveRecord for example, treats SQL as “the source of truth”, per his definition.

Also, there is a whole section called "ORMs take over connection management and migration" and not a single point as to why connection management handled by the ORM is bad, only comments on migrations. Moreover, migrations are not an unsolved problem at all.

In Ruby, ActiveRecord solves all scenarios I have encountered gracefully, and trust me, I have dealt with tons of edge case projects.

For Python, I recommend Alembic: https://pypi.org/project/alembic/

Re:ORMs Are Backwards by zzzeek on Tuesday September 03, 2019 @09:46PM (#20873236)
> ActiveRecord for example, treats SQL as “the source of truth”, per his definition.

SQLAlchemy author here. I can't imagine how someone would not consider SQLAlchemy to treat "SQL" as the "source of truth" as well. All of SQLAlchemy's constructs map directly to SQL syntactically. Our most grumpy users are the ones who don't know SQL. This blog post would certainly benefit from some actual examples. "ORM-light tools that coerce responses into native structs and allow for type-checking are less offensive to me." - that is...an ORM?

Re:ORMs Are Backwards by taffer on Tuesday September 03, 2019 @10:33PM (#20873459)
What the blog probably means by "SQL as a source of truth" is that you first design your database and then generate the domain classes from it and not vice versa. This is for example what myBatis and JOOQ do.
Re:ORMs Are Backwards by deathanatos on Tuesday September 03, 2019 @10:57PM (#20873601)
I don't think there is enough information from the database schema to feed an ORM reliably. E.g., take a "boolean" in MySQL: MySQL doesn't have proper booleans, it just represents them as a `tinyint`. But you'd really like your ORM to convert those to your language's `bool`!

There is certainly a lot of information in a schema, and you definitely want your ORM's idea of the database and the database's idea of it to be in close alignment, but I feel like any serious attempt would find all sorts of holes like the above when it actually came to doing it.

Re:ORMs Are Backwards by setr on Wednesday September 04, 2019 @12:19AM (#20873969)
Wouldn’t you just offer user-modifiable translators for such cases (presumably with some sane default)? It seems more preferable than writing the domain model in code first, resulting in N copies of the schema definition for N applications utilizing the db..
Re:ORMs Are Backwards by skissane on Wednesday September 04, 2019 @01:21AM (#20874228)
> N copies of the schema definition for N applications utilizing the db..

Is multiple applications talking to a shared DB schema a common practice, especially nowadays?

In my mind, each app/service should have a DB schema which only it talks to, and other apps/services needing data in that DB go through services exposed by that app/service (REST, RPC, GraphQL, whatever) rather than talking to its DB directly. That means you can rearchitect the DB schema, change which DB you use completely, etc., and only the app/service which owns that DB needs to be modified.

Re:ORMs Are Backwards by jacobush on Wednesday September 04, 2019 @02:22AM (#20874519)
Yes, it is common practice just about in every organically grown system, ever. Also anything corporate.
Re:ORMs Are Backwards by falcolas on Wednesday September 04, 2019 @10:18AM (#20877417)
I've done this repeatedly with SQLAlchemy. It does require that I do a bit of additional record keeping in my models, but it's not exactly hard to do.
Re:ORMs Are Backwards by jayd16 on Wednesday September 04, 2019 @04:30AM (#20875127)
Why use an ORM when I have to know SQL and its a direct syntactical mapping?

>"ORM-light tools that coerce responses into native structs and allow for type-checking are less offensive to me." - that is...an ORM?

Yeah...its a light ORM that focuses on turning a result set into an object but not the syntax remapping of queries. Object mapping is almost universally liked but ORMs usually include query syntax mappings and not the addition of a transaction lifecycle into your data objects.

Re:ORMs Are Backwards by zzzeek on Wednesday September 04, 2019 @09:56AM (#20877222)
because an ORM has nothing to do with writing your SQL for you. You can use textual SQL with an ORM and an ORM like SQLAlchemy has a query language that mirrors the structure of SQL in any case. nobody is taking your SQL away. Additionally, you most certainly do want a tool that will write most of your DML for you, there is no deep wisdom or joy in writing the same INSERT / UPDATE statement over and over again.
Re:ORMs Are Backwards by cozos on Wednesday September 04, 2019 @01:12AM (#20874187)
I've used SQLAlchemy a bunch and found that it has excellent support for complex (for me anyways) query options. For example, avoiding N+1 selects with eager joins has been a breeze.
Re:ORMs Are Backwards by Diggsey on Tuesday September 03, 2019 @08:56PM (#20872973)
ActiveRecord does not treat SQL as "the source of truth". ActiveRecord objects maintain their own state, and still try to be a "proxy" for rows in the database. When you call "save", it's tracking the identity of the object using the primary key, and generating SQL from the runtime state of the object. This is backwards.

The biggest problem for me is this "proxy" behaviour. When you start treating objects as proxies for database rows, your database logic inevitably leaks into your business logic. Someone returns an ActiveRecord `User` object from a database method and now random other parts of your application are intricately linked to your database implementation.

On top of that you suffer terrible performance problems. Even if you're smart enough to avoid the whole N+1 query issue, you lose control (and more importantly visibility) over where database updates are coming from, and your ORM is either not smart enough to efficiently apply updates, or so smart that building the query takes longer than actually running it! (I have this problem with SQLAlchemy right now, where it's taking several seconds per request CPU bound in the ORM). Undoing this is literally impossible without a near total rewrite.

And it all serves no purpose! The application-specific interface to its database is usually quite simple. You can just have it be a literal interface/trait/whatever and implement each method by executing a bit of SQL, or calling a stored procedure, or however you like. Arguments and return values should be plain-old-data (no magic proxy objects) and each method should correspond roughly to a transaction so that the rest of the application doesn't have to worry about that database-specific stuff.

There are a ton of other benefits to doing stuff this way, too many to list here, but it really helps with complex migrations (like if you need to migrate from one database to another), maintainability, testing, etc.

Re:ORMs Are Backwards by zzzeek on Tuesday September 03, 2019 @09:38PM (#20873192)
> (I have this problem with SQLAlchemy right now, where it's taking several seconds per request CPU bound in the ORM). Undoing this is literally impossible without a near total rewrite.

for the write? writes are not usually much of a performance issue except when people are inserting thousands of rows. You can replace flush with direct INSERT/UPDATE or use the bulk API. Pre-setting primary key values will also improve performance by an order of magnitude. Share some of your code and profile results on the mailing list and we can look into speeding it up.

SQLAlchemy always provides many techniques to optimize areas that have performance problems, not to mention the primary direction for most major releases is that of new performance features and improvements year after year, despite having to deal with the dog-slow cPython interpreter. No "total rewrite" of your application should be needed.

Re:ORMs Are Backwards by Diggsey on Wednesday September 04, 2019 @06:10AM (#20875641)
The SQLAlchemy ORM is a very complex beast. It provides plenty of methods to optimise the queries it generates. It provides no way to "magically speed up the python code".

This is not just for writes, but also for loading complex relationships from the database: we've gone to great lengths to ensure we're using relationships optimally so as to cache query results and not fall into the N+1 query problem, but our application spends all its time inside SQLAlchemy.

The only way to get the performance improvements we needed was to stop using it as an ORM altogether. However, the ORM was too intricately tied with our business logic to do that without a near total rewrite.

What we've actually ended up doing to do this rewrite more incrementally is to create a Rust service which implements just the "GET" end-points: this gives us an order of magnitude performance improvement despite it using the exact same database and fetching the exact same data. Most of the business logic did not need to be duplicated as it is not used from "GET" requests, but users of our application will get much snappier loading times, and it will generally feel much better.

Re:ORMs Are Backwards by zzzeek on Wednesday September 04, 2019 @10:11AM (#20877357)
> It provides no way to "magically speed up the python code".

it most certainly does provide many techniques to reduce in-Python computation. Have you read the performance section in the FAQ and worked through the examples given ? Have you looked into baked queries , querying for columns and not objects ? These are often quick wins that make a huge difference. The baked queries concept is going to be much more cleanly integrated in an upcoming release.

> This is not just for writes, but also for loading complex relationships from the database: we've gone to great lengths to ensure we're using relationships optimally so as to cache query results and not fall into the N+1 query problem, but our application spends all its time inside SQLAlchemy.

that's typical because a CRUD application is all about its object model and SQLAlchemy is doing all of that work. if you wrote your own object layer using Core then you'd see all the time spent in your own objects<->core layer. This is just the reality of Python.

Here's a runsnakerun I make some years ago of the PyMYSQL driver loading data over a network-connected database: https://techspot.zzzeek.org/files/2015/pymysql_runsnake_netw... Notice how just the Python driver spends TWO THIRDS of the time an the actual waiting for the database 1/3rd ? that's Python. If the database is on localhost, that pink box shrinks to nothing and all of the time is spent in Python, just reading strings and building tuples. It's a very slow language.

Rust language however is blazingly fast and is one of the fastest languages you can use short of straight C code. This is not the fault of an ORM, this is just the reality of Python. You certainly wouldn't think that if you used an ORM that runs under Rust, it would be as slow as your Python application again. It would continue to be extremely fast. The Hibernate ORM is an enormous beast but runs immensely faster than SQLAlchemy because it's running on the JVM. These are platform comparisons. SQLAlchemy is really fast for the level of automation it provides under pure Python. You made the right choice rewriting in a fast compiled language for your performance critical features but that has little to do with whether or not you use an ORM. As long as your database code was in your Python application, you'd end up writing an ORM of your own in any case, it would just be vastly more code to maintain.

Re:ORMs Are Backwards by Diggsey on Wednesday September 04, 2019 @02:15PM (#20879916)
> it most certainly does provide many techniques to reduce in-Python computation. Have you read the performance section in the FAQ and worked through the examples given ? Have you looked into baked queries , querying for columns and not objects ? These are often quick wins that make a huge difference. The baked queries concept is going to be much more cleanly integrated in an upcoming release.

Most of those techniques boil down to "use SQLAlchemy as a query builder rather than an ORM" - something I wholeheartedly agree with and would love to do, but it is next to impossible because these ORM objects have leaked into the business logic of the application.

> that's typical because a CRUD application is all about its object model and SQLAlchemy is doing all of that work. if you wrote your own object layer using Core then you'd see all the time spent in your own objects<->core layer. This is just the reality of Python.

I'm not trying to say SQLAlchemy is a bad implementation - it's a great implementation with tons of powerful features. I just believe that using anything as an ORM is a bad idea and directly leads to these performance and maintainability issues. Whether that's ActiveRecord, SQLAlchemy or even if someone implemented a similar ORM in Rust.

Some of the performance issues are due to python, but that's not the whole story: if I use SQLAlchemy as a query builder, and only return plain-old-data, I do not see nearly the same performance issues. Not because SQLAlchemy ORM is badly written, just because it has to do more work, create more objects, with more "magic properties", maintain more entries in the session, etc. etc.

Re:ORMs Are Backwards by wavesplash on Tuesday September 03, 2019 @09:19PM (#20873104)
While you're not technically wrong at the SQL level, I believe there's a higher level of business problem that ORMs like ActiveRecord solve for that's being overlooked. (aka ActiveRecord got Airbnb and Shopify a long way in scaling both developers and product).

CPU time building a query is rarely the business bottleneck until you're at a huge scale - scaling up the engineering team uniformly and having business logic at abstraction level closer to the rapidly evolving Product specs is the bottleneck that ActiveRecord solves for, and does pretty well.

If you'd like something that avoids object/hidden state - check out how Elixir/Phoenix's Ecto[1] was designed - it avoids many of the shortcomings of ActiveRecord and SLQAlchemy:

1: https://hexdocs.pm/ecto/Ecto.html

Re:ORMs Are Backwards by kangoo1707 on Wednesday September 04, 2019 @03:54AM (#20874936)
+1 for Ecto, the most powerful ORM that I have seen so far (among PHP, Node, Ruby, Python and Elixir)
Re:ORMs Are Backwards by jrochkind1 on Tuesday September 03, 2019 @09:30PM (#20873161)
If "SQL is the source of truth" means you have no in-memory state, I'm not sure that's feasible, with or without an ORM.

Once you fetch something from the db, you have it in variables in memory. With or without an ORM. That could no longer be sync'd with the db a ms later. That you will probably re-use for more than one access (if you need it more than once), because issuing the same SQL again the second time you need to reference any data returned, in a given routine, would be insane.

Re:ORMs Are Backwards by derefr on Tuesday September 03, 2019 @09:40PM (#20873204)
I think what they’re proposing is something like “if I want to read and then update a value, incrementing it by one, the proper way to do that is to tell the DB to increment whatever value it has stored; not to increment my local representation of the value and then tell the DB to overwrite the value with it.”
Re:ORMs Are Backwards by jrochkind1 on Tuesday September 03, 2019 @09:48PM (#20873251)
The number of times I've had to increment a value by one are minimal. Or similar things where you want to update one value based on the other values, and do it in an atomic way.

But if I had to increment specifically, and were using ActiveRecord... I'd use the ActiveRecord increment! method. Which has the proper db-atomicity semantics you want, it does execute `UPDATE x SET n = n+1 WHERE pk = y ...`

Re:ORMs Are Backwards by Scarblac on Wednesday September 04, 2019 @04:42AM (#20875200)
ORMs do that fine, for instance it'd be

    MyModel.objects.filter(pk=...).update(myfield=F('myfield') + 1)
In Django's ORM.
Re:ORMs Are Backwards by rubber_duck on Wednesday September 04, 2019 @05:30AM (#20875447)
This is not obvious unless you think about it - I've seen issues like this because the code looks correct if you ignore the update semantics, especially when it wont fail on your local dev env because in memory state will be the same as DB state since you're the only one hitting it...

Having to think about these things because the abstraction is leaky instead of just using the SQL where the transformation is explicit is not worth the hassle.

Using Clojure and yesql was a real pleasure for this - Clojure is immutable so you're working with values all over the place - and working with SQL query is just passing values in it and getting values out - no magic mapping - it's just values - if you want the latest value - query again, if you want to store a new value - send the new value. No mutation, no magic, just data.

Re:ORMs Are Backwards by jayd16 on Wednesday September 04, 2019 @04:58AM (#20875283)
So obvious and simple! ...or you could just use SQL.
Re:ORMs Are Backwards by ben509 on Wednesday September 04, 2019 @09:23AM (#20876931)
> Once you fetch something from the db, you have it in variables in memory. With or without an ORM. That could no longer be sync'd with the db a ms later.

Nope. The DBMS is making the same transaction isolation guarantees to an ORM as to any other client.

I think the larger "source of truth" issue is how the schema is represented.

All of these systems that provide their set of schema objects that are then pushed to the database. This works fine for LAMP stacks, but as soon as you grow it becomes apparent that you've put the horse before the cart.

Re:ORMs Are Backwards by joevandyk on Tuesday September 03, 2019 @08:58PM (#20872991)
I’ve found that you want to be super careful in application code about where you do updates and inserts when using AR.

Sprinkling calls to order.save! or order.update(foo: bar) in random places is a recipe for disaster.

Re:ORMs Are Backwards by jayd16 on Wednesday September 04, 2019 @04:55AM (#20875271)
>Someone returns an ActiveRecord `User` object from a database method and now random other parts of your application are intricately linked to your database implementation.

Indeed. Its a rookie mistake but hey, you work with rookies all the time! Orms can be useful but they certainly allow for some dangerous patterns that are hard to notice if you're a young developer.

Re:ORMs Are Backwards by afcapel on Tuesday September 03, 2019 @08:51PM (#20872942)
ORMHate is nothing new under the sun. Martin Fowler wrote a more balanced article about the issue years ago https://www.martinfowler.com/bliki/OrmHate.html
Re:ORMs Are Backwards by vinceguidry on Tuesday September 03, 2019 @08:48PM (#20872920)
They mention the case where the ORM shadows the db schema. The argument isn't terribly salient, it's not like you can't build type checking in, it just means more work if that's what you want to do. Essentially according to the author the only way to do it right if you have an existing db is to write all queries from scratch.

My preferred solution for existing DBs is Ruby's Sequel or ROM if you want to go nuts. But I'm a diehard Rails lover and will choose ActiveRecord over all other solutions if I can. To me there's only one decent ORM and everything else is either good for special purposes or junk.