OrmHate (2012)
martinfowler.com
martinfowler.com
In application development, it's gotten very popular to ignore the data storage component of application design, or at least defer it to the last possible moment. React et al take things even further by requiring their own modeling logic.
As many here would agree, relational dbs never went away. But for some reason folks stopped trying to push the envelope with schema-driven application development. I kept expecting to see iterations of schema-driven design and layout system (using inputs derived from table fields, etc). What we got instead is this ORM business. Is there a schema-first tooling community that I'm unaware of?
If I had a nickel for every time I've seen code using an ORM framework fetching a set of ids and then iterating over that set to fetch a single record at a time. And then if I had another nickle for each blank stare I got when I explained that only one SQL query was needed to return the flattened result set of data and that the hierarchical object graph could be built in the app much faster than the N SQL queries they were making.
SELECT DISTINCT p
FROM Player p
WHERE p.teams IS NOT EMPTY
Or navigation through a path (i made this example up, might not be quite right): SELECT t.name, t.league.sport
FROM Team t
WHERE t.city = 'Ul Qoma'
If you're doing reporting where it doesn't make sense to pull out the entities, you can use a constructor expression [2] to materialise results as anything else you like (again, made up, might not be spot on): // package hn; public class CountSummary { public CountSummary(String name, int count) {} }
SELECT NEW hn.CountSummary(l.name, COUNT(l.teams))
FROM League l
Certainly, there are more sophisticated features of SQL that are useful for analytical queries that aren't in this query language. That's why every ORM has an escape hatch where you can write raw SQL, but still use its machinery for turning results into objects.[1] https://docs.oracle.com/javaee/6/tutorial/doc/bnbtl.html
[2] https://docs.oracle.com/javaee/6/tutorial/doc/bnbuf.html#bnb...
Even aside from this N+1 problem, to use an ORM well, you have to understand the SQL that you want to be generated, and then instead of writing that SQL, you have the additional problem of having to know exactly what to say to the ORM to get that SQL generated. Also: you don't always want objects returned; sometimes partially instantiated objects are what you want; bulk updates are hard to get right; stored procedures break everything; and on and on.
The thing that really started me on the path to my current view was a conversation with an app developer who was evaluating our project. Just from a career point of view, having Oracle on her resume was a lot more valuable than having OurProprietaryORM on her resume. That, combined with the realization that solving the N+1 problem was really a pretty minor thing, and that all the other problems were even harder to solve.
The approach I have been using for a while is to use a very simple mapping layer that hides some of the ugliness of JDBC, but still lets me write SQL directly. I understand that MyBatis does something like this, although I haven't used it.
The ironic part? The folks who do that tend to be the same folks who swear the ORM will pay for itself by query-optimizing under-the-hood. 9_9
By "interesting cases" I mean applications that leverage the more unique features in different database systems (like the full-text search implementations, that tend to be very database specific). By subtle differences I mean e.g. specific issues like Oracle handling NULL vs. empty string unlike other databases, or more serious differences related e.g. to locking (so an application that works fine on one database fails spectacularly on another one).
Obviously, if you treat the advanced database system as a dumb data store, this may work. But it kinda tends to reduce any database to the worst non-existent database, missing any of the nice features. Because all the advanced stuff is "forbidden" for portability reasons. I've seen so many cases of performance issues where the right solutions were rejected because it would impact portabiliy ...
There are certainly use cases where ORMs are not appropriate at all but the ability to evaluate your situation and recognize the tools that are appropriate and those that are not is an important skill in the software industry in general and is not limited to ORMs.
Actually, most ORMs like Hibernate allow you to write native SQL (or even stored procedure) to take advantage of native db features. In that case, you have to trade-off portability, if that feature is important to you.
>> or more serious differences related e.g. to locking (so an application that works fine on one database fails spectacularly on another one)
Locking (default - READ_COMMITTED) also works consistently across databases. The problem happens only when you introduce distributed ORM cache where data is not updated synchronously across the cluster. It is a reasonable trade-off for performance.
I've never worried much about portability, but i've really appreciated an ORM saving me writing all the tedious boilerplate for turning ResultSets into objects, and objects back into parameters to update statements, particularly when changing collection properties.
Somehow I became the "database guy" of my workplace (small startup without a dedicated DBA) and everybody thinks I really dislike our ORM because all they hear me say about it is pretty negative. However, that is because nobody comes to me with the 95% of easy questions so every question I get is one of the 5% edge cases where the ORM is really not a good fit :|
The fact that you can use the same Linq expressions in your code and they can be translated to in memory Lists, Sql, or even MongoQuery at runtime is beautiful.
I've used DataMapper and Sequel, and read through the ROM docs, I don't think any of them really bring something better to the table than good, old, solid Active Record. Linq is expressive and fun, compared to .NET. Nothing compared to Ruby.
As abstraction layers go, I can't ask for a better one.
I've used both ActiveRecord and Entity Framework extensively. And while Entity Framework definitely has it's drawbacks, it doesn't get any better than Linq in combination with static compilation for a large and complex code base with a lot of moving parts.
And yes, "don't do that" is all well and good when it comes to the 'right' way to do things in Rails/ActiveRecord, but when DHH himself is driving the callback train at full speed and you take a project over from someone who jumped on the bandwagon and didn't actually know how to properly apply these tools to the code, it's going to be a pretty nasty wreck.
You can be damn sure I'd rather take over and refactor a large, messy C#/Linq/Entity Framework project than a large, messy Rails/ActiveRecord project.
Also I could never get over the fact that Rails developers for some reason treat foreign keys like it's ebola. "It's just not the Rails way" is not a good answer for not using foreign keys.
It is also a bit overzealous in being database agnostic. For example, MySQL allows for LIMIT clauses on UPDATE and DELETE statements, which is super useful for making sure you don't accidentally create super long running transactions because there were many more rows on the other side of that relation than you expected. However, if you want to use that from ActiveRecord you need to drop down to custom SQL, because not all databases support that SQL pattern.
SQL Alchemy https://www.sqlalchemy.org/
I don't know Linq personally but I know people liking it very much -- your comparison is probably accurate.
* No support for "OR" statements, without dropping down to raw SQL (I think this was added in either Rails 4, or Rails 5)
* Terrible support (if any) for more advanced PostgreSQL features, such as CTEs, bigserial (Rails 5 finally supports this if I'm not mistaken), no proper support for composite primary keys, no support for PostgreSQL CHECK constraints, no support for index expressions (e.g. an index on `lower(foo)`), etc. For GitLab we had to add many hacks to work around this.
* A rather messy model API, where models are used as both repositories and objects representing single rows. Sequel makes the same mistake with its ORM, but at least you can use the query toolkit separately. ROM supposedly does this better, but I have not tried it.
* Too many methods are injected into your classes, and you'll probably never use most of these (this is a more philosophical issue, and certainly not a deal breaker).
There are plenty more, but these are the ones that come to mind. My biggest issue in recent times was caused by ActiveRecord/Arel silently throwing away CTEs used in UPDATEs. Basically in GitLab we did something like this (I don't fully remember what the query was):
WITH foo AS ( ... )
UPDATE some_table
SET a = b
WHERE x = foo.y
In other words: we'd grab some data using a (recursive) CTE, then update a bunch of rows based on that data. Unfortunately, ActiveRecord/Arel would throw away the CTE in certain cases. This would result in us effectively running the following: UPDATE some_table
SET a = b
You can imagine the fun of having to deal with all rows being updated unexpectedly. More details on this particular bug can be found here: https://gitlab.com/gitlab-org/gitlab-ce/issues/37916Long story short, I'm fairly convinced that using Sequel would have made GitLab's database code much more solid/less buggy than it is today, simply because it supports so much more (and better) out of the box.
ActiveRecord always did the right thing, and whenever I looked at the generated SQL I was pleased.
That said, they're both enormously productive.
Recently I went looking for a schema generation tool, found one that went and looked at an existing database, and generated scaffolding commands to create models and if necessary, controllers and basic CRUD. Only thing it didn't do was has_many associations, of course you could just add those in before running the generated commands. You just can't find this level of refinement anywhere else except maybe Java, of course then you'd have to use Java.
The biggest problem I often see in large DB systems is the lack of proper normalization, leading to very wide tables and subsequently causing performance problems (both due to reduced scanning speed and ORMs tendency to just pull in all attributes of selected rows).
Another problem with Hibernate is the proper demarcation of transactional boundaries, causing DB inconsistencies. Very important to get right.
I could go on, but then this would become a blog post. In any case, I feel that a functional mapping over the SQL AST and result sets using Scala and Slick (for example) leads to much higher versatility, places computation closer to storage and provides better abstraction possibilities. This, in turn, reduces the complexity of the middle layer.
How does Hibernate as a project (or the community around it) develop people who have expert knowledge in it and/or general cache management?
It's common, then, that the low hanging fruit for a contractor coming in to improve performance is to optimize queries and/or introduce triggers, views, and stored procedures. I've found that devs are scared of plain old SQL but the ones who know their ORM well can learn SQL easily with a little nudge and some guidance.
What am I supposed to know about this? I've only used an object database for one serious (>100KLOC) project, but it was by far the best part of that system. I always wondered why that architecture never caught on.
The only reason I'm not using an ODBMS on my own projects today is that there's no good open-source one, but that seems like something a company could do something about, and a lousy reason to condemn an entire architecture.
Lack of a good query language and scalability issues doomed those. Mike Stonebraker (https://blog.grakn.ai/what-goes-around-comes-around-52d38ee1...) had a nice summary in that article.
Interestingly, the issues are largely C++-specific (and I know lots of people using an ORM today but none via C++), and largely driven by historical accident and market forces ("It is interesting to conjecture about the marketplace chances of O2 if they had started initially in the USA with sophisticated US venture capital backing").
I still don't think these sound like good reasons to dismiss the architecture.
(Disclaimer: I am the founder of OrientDB)
Now, instead, everybody deals with the object/ORM mapping problem by writing their own framework.
If it is entirely in sincerity, I don't understand why he thinks queries isn't the de-facto solution. Perhaps his implicit assumption is that the database should be structured to reflect the code, when really the code should be structured to reflect the database?