6 ms·
The article makes a strong case against ActiveRecord, not against Object Relational Mapping. Under the heading "The problem with ORM" the author writes, T
by cturner 15y ago
The article makes a strong case against ActiveRecord, not against Object Relational Mapping.
Under the heading "The problem with ORM" the author writes,
The most obvious problem with ORM as an abstraction is
that it does not adequately abstract away the implementation
details. The documentation of all the major ORM libraries is
rife with references to SQL concepts. Some introduce them
without indicating their equivalents in SQL, while others
treat the library as merely a set of procedural functions for
generating SQL.
This is true of ActiveRecord, it's untrue of object graph ORMs like Apache Cayenne.
I find two patterns to be key to effective ORM:
* Data Access Objects. This is for when you have nothing, and want to get an entrypoint into the schema. In this case, you should be able to write near-pure SQL to get what you want.
* Entity Objects. This is what the DAO will give you back - either an individual or a list. Each instance represents a row in a table, and has methods that will do lookups to foreign keys. Once you have this, you have an entrypoint into the data graph, and can use foreign keys to crawl around to wherever you need to go.
The DAO layer is a simple, centralised place where you can implement permissioning logic.
If you need to do something high-performance (usually some sort of report), you create a custom DAO, and have it return custom entities (instances of classes that don't have 1 to 1 association with a table) that fit your need.
I've found that after a certain point of complexity in an application, it becomes impractical not to use an ORM. It's like working in a type-unsafe language. You refactor something, and SQL-in-code breaks all over the place. That path leads to the hiring of dedicated DBAs, and abstraction of the schema behind stateless layers of PL/SQL in a doomed attempt to get to grips with the complexity of the problem space.
I worked on a system with a very tough customer where they repeatedly demanded major schema changes that were sitting in front of a business logic layer and frontend that had already been written. While the project had lots of problems, those particular refactorings were very straightforward. I was able to modify the ORM, and then just fix complilation problems and a few obvious tentacles from them until the application recompiled, at which point it worked again.
Some more criticism:
This leads naturally to another problem of ORM:
inefficiency. When you fetch an object, which of its
properties (columns in the table) do you need? ORM can't
know, so it gets all of them (or it requires you to say,
breaking the abstraction).
I'm rusty but remember that at least in WebObjects EOF at least you can nominate what you want to retrieve, including automatic joins to retrieve stuff over foreign key jumps
The author's first suggested alternative "Use objects" offers worse technical debt than ActiveRecord. I anticipate there are a lot of shitty systems being on top of key-value stores. You can get fast results doing it, but it has technical debt and doesn't scale horizontally. The key-value store is becoming the next generation equivalent of "Oh we'll just build it in excel, and worry about the consequences later on". But depends what you're doing. There are situations where foreign keys are good.
The second alternative is "Use SQL in the Model" The advice of the heading doesn't match the content of the text that follows. I think the author means to recommend building a service that wraps the model by answering questions. If not, that's the point I think that should be made.
It's common for companies to create a database, and then have many entrypoints into it. This is a mistake and creates technical debt. As soon as you have multiple entrypoints like this, you lose ability to refactor your schema (because it's impractical to get multiple stakeholders to make concurrent changes) and your system rots.
Instead, you create a model service that wraps the schema, but also has stateful knowledge. For example - it knows the permissions of the user who is talking to it and can tailor its response based on their permissions. Then you return results in a transport format. I can't recommend a good, mainstream mechanism for this. JSON, YAML are fiddly because they're typed, XML is unnecessarily verbose
Anyway - there's no reason not to use a good ORM in this business logic layer. For small systems - sure - use SQL in the model. For the larger stuff, you have a more maintainable system if you use an ORM. But if it's a complicated space, steer towards Cayenne or Hibernate, rather than active record patterns.
- bad_user 15y agoI never found anything wrong with Active Record patterns, in all the projects I worked with. Of course, it depends on the ORM and it's a pattern that blends itself especially well with dynamic languages (IMHO, Hibernate sucks). Of course an ORM will have SQL concepts because that's the nature of the beast. You can't abstract those away. There is also nothing wrong with generating SQL for you, as generating SQL by hand is a task that involves string concatenation. And when adding filters to that SQL based on lots of runtime conditions, it gets freaking painful to do it - it's a lot nicer to work with expressions that are building a syntax tree, to which you can add and add whatever you need. Also, about efficiency, the dumb ORM in Django can do this (prefetching relationships on one level): user_rs = User.objects.filter(groups__name='Badass')\ .select_related(depth=1) It can also do this: rs = user_rs.values_list('groups__name', 'username') Which returns an array of (group, name) tuples. No inefficiency there and no leaking abstraction either. You can also drop to pure SQL, if you're so inclined for efficiency reasons. In that SQL you can also restrict the columns you're selecting, and so the model objects returned will lazily load missing attributes on request ... user = User.objects.raw('select id, username from auth_user limit 1')[0] # already retrieved user.username # this works, but a second query is made user.first_name So, yeah, people can use raw SQL or whatever fad-du-jour they want. I'll just be busy working on stuff.
- cturner 15y agoThe way I think about it, active record is the strong option for crud, but doesn't scale well past it. Once you're dealing with many tables I think you need the ability to accumulate a graph representing a new state and then commit it as a transaction. While you could do this in an adhoc manner with active record, it's not a way of life in the way it is in EOF/Cayenne. There are complications of the object to relational mapping that it doesn't deal with. For example - no mechanism for dealing with situations where someone change data underneath the object graph a user is working against in memory. AR would blast over the top of the data with what it has in memory, potentially breaking in the process. Some ORM systems have locking levels. For example, with 'optimistic locking' you get an exception if data changed underneath you. It's handy to be able to catch an exception, email the support team, and tell the user to freeze and contact them, in a way that doesn't discard the data they've just entered. I find it backwards that active record exposes methods like insert, delete, update. These are low-level sql concepts, and you shouldn't need to care about them within an object system where you're thinking in terms of a graph of data. It's very wired to the database though. As far as a "mapping" between object and relational, active record is crudely simple. On a positive note, you can hammer an AR out in your language of choice from memory, without reference to any external libraries or the like. It's a good wrench in the toolbox.