What ORMs have taught me: just learn SQL (2014)
wozniak.ca
wozniak.ca
I quickly ended up with a lot of duplicated code. So then I thought, "Well ok, I should add a bit of abstraction on top of this..." I started coding some simple functions to help map the tabular data to objects. One thing led to another and suddenly I looked at what I had done and said, "Wait a minute..."
Seriously Jesse, isn't this the very same reason __some__ people end up implementing yet another programming language without realizing it?
First they start out of exasperation with X language they use, because they hit some obstacles or limitations, and before they know it they end up implementing a newly created language.
You know what's the fun part? In their attempt to fix the aforementioned language's issues, they end up introducing __the very same problems__ in their own language, only under different "cloak" so to speak.
It's a vicious cycle I'm afraid...
Micro ORMs.
A lot of people conflate ORM's leaking because of poor designed library with ORM being a bad abstraction in general.
There is no real abstraction. You write standard SQL and it maps your recordset to an object. You know exactly what code is running.
There are extensions that will take a POCO object and create an insert statement and I believe updates, but where ORMs usually get obtuse and do magic are Selects. It’s hard to generate a suboptimal Insert or Update.
There are times I dislike things about it, and it can be quite heavy, but it’s very easy to mix and match ActiveRecord ORM code and raw SQL even within a single model class.
> Micro ORMs.
And there's the mystical fourth option of simply not bothering with objects in the first place. No objects, no need to do object-relational mapping.
Nothing says the structure which make sense for an application can't be a generic array of generic dictionaries.
If your favourite programming language forces you to go through the silly hoops of data mapping in order to do useful things with query output, I can understand why an ORM might make sense for you.
I've used hand rolled pseudo ORMs before.
I prefer just plain SQL but for the application I had there was a common access pattern that was worth abstracting out in a DRY sense.
That doesn't mean I want or need a complete ORM. Just a consistent access at certain table types.
There is usually a feature that no one thought about and then you have to make modifications to the custom ORM and you get an even bigger mess.
I wouldn't say what I built was a better ORM. But I would 100% say it's a better solution to the problem I faced.
It didn't get in the way of writing SQL, but it did reduce the boilerplate and repetitive gruntwork.
"Issues" - no, it was a function call deep and incredibly clear and close to what was happening.
Is your pseudo ORM as well documented as a commonly used ORM? Can I google (or search your wiki) for common issues?
Having written OSS and also having written enterprise applications, it seems plainly obvious to me why a homegrown solution is preferred. Code developed internally is understood by the team (you may not understand the underlying implementation of a dependency), and can be tailored exactly to suit your needs (ignoring edge cases that aren’t relevant, removing unneeded features). And you never have to worry about maintainers disappearing, breaking changes being introduced, or bugged releases that you can’t do anything about.
I don’t mean to sound crass but how on earth could you think this is an example of Poe’s law? What’s so extreme about being a responsible developer? I didn’t say “every solution should be developed in house” (though I think most large projects would be better for it!) obviously there’s is a cost associated with in house solutions and you should gauge that cost to see if it’s worth it for your application. But if you’re going to be working with that application for years and years to come then I highly recommend trying to write your own code instead of relying on libraries.
1. A high level database or queue lib, or a custom / powerful serialization lib or, relevant to this topic) an ORM or other foundational/low-level part of your tech stack.
What you can expect to happen is a bunch of very good programmers early on build powerful abstractions using macros, metaprogramming, advanced type system concepts and build up a codebase adding up to a few thousands of lines. It just works, it's a good system - a few bugs are patched by the team every month, but that's fine. Fast forward a few years, the programmers have moved on, "onboarded" the rest of the team to the codebase during their respective last week, but given how complicated the codebase is no one is really capable of debugging it and fixing issues. And given that it's not open-source, it never got an opportunity to build a community of contributors. Your team is now SOL, and it's going to take _months_ to replace it with a more well-maintained open-source solution.
2. Building a A/B testing implementation in-house - again a couple of good programmers build a working, scalable, basic system in a weekend. It actually works and the code is good, simple, readable and well-tested. But then, your PM team or your Marketing team wants you do add graphs. Then export the data to RedShift. And then tweak the algorithms powering the backend. Then multi-arm bandit. And so on. Now, what was now a weekend project, turns into months of work - whereas there exist paid services that do this for you.
Sometimes, it's unavoidable, external alternatives are genuinely not good*. But I strongly think, you have to be very, very careful about building systems in-house when they are not your business.
> I don’t mean to sound crass but how on earth could you think this is an example of Poe’s law.
Sorry for this. I do feel quite strongly against your original comment (at least the way it was written without context), and I think it's the _opposite_ of being a "responsible developer" in all but edge cases, and think you are wrong. But calling it an example of Poe's law was not right on my part, and was harsh.
> But if you’re going to be working with that application for years and years to come then I highly recommend trying to write your own code instead of relying on libraries.
I've done this, and have done both in-house and oss code, but in-house, very reluctantly - for example - when there's just one or two maintainers committing code, and there's no alternative. But even then, I have usually forked the code and used that or parts of that as the base, rather than starting from scratch
Which bring us to the topic of tradeoffs and the synthesis of balance, by way of weighing competing advantages and costs fairly.
On the one hand, code you must write and understand. On the other, code someone else wrote, that you can just use. There is no clear winner here. It's always a tradeoff.
It’s really a balance, but I don’t think it’s a balance most devs consider and they really should.
I rewrote our entire database layer in Hibernate for our (incredibly complex monolith) webapp. Then I was tasked with rewriting a major core piece of search functionality that builds a query from user selections / saved queries.
I was told that contrary to previous work, Hibernate Criteria would not be allowed, since it was deprecated. Hibernate's official replacement for programmatic queries is JPA Criteria, but Hibernate's support for this was not feature equivalent to Hibernate Criteria, so this was out too.
So what I got the green-light to go on was rewriting my own pseudo-ORM wrapper that generates HQL query strings and parameters. Hql is not deprecated, you see.
It's ended up working out moderately well, it's a thin layer and as long as you avoid the rough edges it actually works fairly well, as well as providing a convenient point to translate query language from the old kodo format into hibernate (cringe, code smell).
There have been times I've had to do some very awkward query shit that I've only managed to lever in via HQL. You have no idea, views on top of views.
No idea what'll happen after I leave, that's their problem!
Thanks for the job security, Hibernate team. Your incredibly-poorly-executed transition from a well-supported standard to the "new new" has been exquisitely great for my job security.
Bet there's Python3 devs who feel the same way!
Feature parity aside, I also found this to perhaps be the most verbose API for query building I've ever used.
Such as:
- API abstraction layers (like SDL, Allegro, SFML, etc.): you want to support new operating systems / new APIs by default. And most of the time, you don't want to spent time learning about the specifics of X11 window creation or Win32 events, as this would be throw-away knowledge anyway.
- hardware abstraction layers: you want to support new hardware by default, this is why we use operating systems and drivers.
- Format/protocols abstraction layers: if your game engine only uses JPEG files directly coming from your in-house asset pipeline, it's perfectly fine to develop in-house loaders (from scratch or from stb_image). But if your picture processing command-line tool aims to support every file format (especially, the ones that don't exist yet), then you should rather go with an updeatable third-party library, which will allow you to get all new formats by default.
- all kind of optimizers, including compilers, code JIT-ters, audio/video encoders, etc. More generally, all code that uses some heuristic so try to solve a problem that's not completely solved/solvable. You might be ready to accept the performance of a specific version of, for example, libjit. But you might instead consider that in your case, not having state-of-the-art JIT performance might be detrimental to your business, in this case you want to get the performance enhancements by default.
It’s crazy to me how many people on HN are ignorant to the costs of third party dependencies and the benefits of in house solutions when building large applications.
If you do test and document your own stuff properly you are in a small minority. Why not release it for others to use?
> Well, redundancy and dependency both have downsides, but in this case...
For example, a company with many data analysts/scientists who may each be writing their own queries. As a basic example, the definition of some “very important” company metric changes, then there would need to be a large number of disperse queries to change.
But an ORM isn’t the answer for the above situation either.
It also sounds like you would be well served using a service abstraction at that point to remove the data layer from client scope entirely.
The "model changes, now we have to change it every where" isn't going to be solved by abstraction, it's only limited by the amount you're willing to limit access to the underlying model, if you need that information, you need to share the model.
The best solution to this I've seen in practice is domain modelling, colocating shared code near other users. When things get too distant you start using anti corruption layers which allows more flexible model changing.
But at the end of the day this is essential complexity, orm, or any other solution is never going to be able to hide the fact that you need information elsewhere in the system to be useful.
Here's your problem
ORMs are terrible in this sort of world since they tightly couple the application to the data. But if you will only ever have one application anyway the abstraction of a separate schema is pointless.
Why is this the case?
1. If you are doing anything interesting, people are going to ask questions about what you are doing, and the best way to answer those questions is going to be by querying your database.
2. One day you might want to rewrite some of your service/s, split them into microservice/s, etc. At that point, there will be a minimum of two services talking to your datastore: the legacy service and whatever you're replacing it with. I suspect any alternative to this arrangement will be an even worse idea, e.g. taking a deliberate outage to perform a likely-irreversible migration.
- The existence of tools that allow structured access to multiple APIs (GraphQL is a nice middle ground between "YOLO any queries you want" and "you only get row-by-row access exposed by the web APIs").
- The existence of data on multiple internal data stores. Analytics folks usually are not prepared to engage with the complexity of data being stored across handfuls or more of different stores with different schemas. The owner of the application knows how to join that stuff better than they do.
- Building intermediate/denormalized stores isn't frowned upon just because analytics shouldn't run ad hoc queries on the main production DBs. Expose change streams or bulk ("too much" data) endpoints and make it easy to load their results into a reporting system, which can be raw SQL. It's not redundant; if you don't do this, the following conversation starts to happen often: Q: "I'm running raw analytics queries on production and it's not quite working, can we just make $substantial_schema_change so my report works/is fast?" A: "No, we explicitly chose not to structure the DB/index/whatever like that because it seriously fucks up a real user access pattern."
They only address it in so far as they push it downstream to the analyst, who as you mentioned, "is likely not better versed in what means what than the developers who work on the application databases."
There's a reason why datalakes exist, and having used them at past N companies, I think this is why data engineering of the BI flavor becomes necessary at the point that reporting becomes critical. An API is strictly worse than a datalake, and it's not hard to set up and maintain the latter. API versioning and communication are for frontend integrations, paid partner integrations and potentially (although I'd probably lean more on gRPC and the ilk) microservice to microservice interactions. But, I like to avoid building unnecessary API surface when I can.
You should not do this. It removes almost all of the benefits of extracting things into a separate service (services should own their data and the only means of accessing it should be via their APIs). That's not utopian; that's one of the main reasons you do a service extraction in the first place.
Don't choose general data access patterns for the infrequent occurrence of cutover. Cutover is when you break a few rules and then immediately stop doing so. Build for everyday access patterns instead (which should be through the API of whatever owns the data--SQL is a powerful language and a really shitty API).
That being said, I personally have found that I do not like OO languages for back end dev and I find that functional languages such as any variety of LISP marry extremely well to the transnational and process oriented nature of back-end systems as well as lend themselves to not having to jump thru hoops to contort the data into relational sets (Clojure's destructuring is an absolute life saver here). I find that there is little to no duplication of code in regards to transferring data to the db. You may want to give Clojure or F# (depending on your stack) a try for your back end and see if it does not alleviate a host of issues with trying to develop a process and transaction oriented system, which most back ends fit that definition.
I find the converse to be true for the front end. I find most attempts to deal with the UI in anything other than objects and components (read jQuery, React Hooks), turns to spaghetti rather quickly.
If you are using OO languages to communicate and transfer data to the DB you may very well be trying to solution for the impedance mismatch that is easily solved by using a functional language.
What alternatives have you tried?
This is one of the reasons I have long been a huge proponent of Javascript despite it warts, as it can be OO when I need it to be OO and functional when I need it to be functional.
You can do that in an OO language, too; relations (whether constant or variable, and whether there data is held locally by the program or remotely, as in an RDBMS) are perfectly valid objects.
Maybe the problem is not that you don't need a mapping layer, but because ORMs are obscure. And maybe they are obscure not because SQL is such a cursed spot, but because object-oriented programming ITSELF drift toward obscurity and magic. Don't you get the same feeling of obscurity about other libraries, e.g. web servers or clients? I often find the bare specs much clearer than (supposedly simplified) OO libraries that implement them.
If you stick to using the ORM for what amounts to (mostly) just PODs, it's syntactic sugar that can really help readability.
As an example, the sqlalchemy docs[0] make this very clear: there's an ORM, but there's also just a core expression library that simply helps you connect to the database and construct queries.
What just blew me away is the thing with the `JOIN` and the `to_jsonb(authors)`, all with complete typing support for the nested author object. I was actually looking to use a classical, attribute driven query generator (with the sort of chaining API everyone is used to: `tableName.select(...coumns)` etc.) for my next project involving to maybe replace/wrap/rewrite a Rails app and its ORM with Typescript and Node. Maybe I'm trying this instead I'm already half sold. Just worried about forcing colleagues having to learn SQL instead of using a fancy wrapper.
> Just worried about forcing colleagues having to learn SQL instead of using a fancy wrapper.
My current team is pretty junior, and I don't see any problem with this. Simple SQL queries are really easy to learn, and complex queries are harder to understand with ORMs than in raw SQL.
Moreover, knowing SQL is a useful, marketable skill that will stay relevant for many years to come. If there's some resistance, I can easily convince the team that going this route will benefit them personally.
Back to the README, there are two questions I'd like to see addressed:
1. Whether `Selectable[]` can be used to query for a subset of fields and how.
2. In the `to_jsonb(authors)` example, what would you get back in the `author` field if there were multiple authors with the same `author.id` value? An array of `author.Selectable` objects? This part is awesome but brittle, isn't it?
I would love to see this move forward! I will definitely play with it and consider it for my next project.
Your second case, if I recall this correctly (ActiveRecord made my SQL skills fade away), this plain JOIN would just return a row with the same book but a different author. `to_jsonb(authors.*)` is just operating on a single row. But what you want is possible (aggregating rows into a JSON object) by using `jsonb_agg`. Whether the lib supports inferring the correct typings for that is another question though.
:)
1. Whether `Selectable[]` can be used to query for a subset of fields and how.
Right — this is not (currently) supported. I guess if you had wide tables of large values, this could be an important optimisation, but it hasn't been a need for me as yet.
2. In the `to_jsonb(authors)` example, what would you get back in the `author` field if there were multiple authors with the same `author.id` value? An array of `author.Selectable` objects? This part is awesome but brittle, isn't it?
Multiple authors with the same id isn't going to happen, since id is intended as a primary key, so I'd argue that the example as given isn't brittle. On the other hand, there's a fair question about what happens for many-to-many joins, and since my use case hasn't yet required this I haven't given it much thought.
type authorBookSQL = s.authors.SQL | s.books.SQL;
type authorBookSelectable = s.authors.Selectable & { books: s.books.Selectable };
const
query = db.sql<authorBookSQL>`
SELECT ${"authors"}.*, jsonb_agg(${"books"}.*) AS ${"books"}
FROM ${"books"} JOIN ${"authors"}
ON ${"authors"}.${"id"} = ${"books"}.${"authorId"}
GROUP BY ${"authors"}.${"id"}`,
authorBooks: authorBookSelectable[] = await query.run(db.pool);
This exploits the fact that selecting all fields is, logically enough, permitted when grouping by primary key (https://www.postgresql.org/docs/current/sql-select.html#SQL-... and https://dba.stackexchange.com/questions/158015/why-can-i-sel...)I'll update demo.ts and README shortly.
I'd argue that learning SQL is essential for any developer.
It's also a "reusable" skill that will stand them in good stead for decades - whereas learning how to use the fancy wrapper is only useful until the next new shiny comes along.
The long-standing ORMs do a pretty decent job of writing efficient queries these days though. You can go pretty far without knowing much and that’s not a bad thing either.
I love how it let's you use SQL, while taking full advantage of TypeScript's wonderful typing system to give you intellisense and compile-time checking. Reminds me a bit of the SQL type provider for F# (which I was amazed by when I first saw it in action).
I really like the way the readme has been written too - it gives a real insight into the thought processes that led to the final result.
Good news. :)
Maybe we can agree that typegen smells less than codegen?
Reminds me a bit of the SQL type provider for F# (which I was amazed by when I first saw it in action).
I should look that up — sounds interesting.
I really think typescript would benefit from a good solution to this.
I am old fashioned. I like to start with the database schema and generate code from that. I make a change in the schema I regen the code. Thanks for partial classes in C# I can persist customizations between code-gens if necessary.
That lastpost you can map a half dozen Java frameworks to each of the acts.
Personally I never found an orm that tracked which attrs in objects were actually mutated so that only mutated columns would be updated/inserted, but again I never did a lot of orm.
const existingBooks = await pool.exe(select("books", { authorId }));
or
const existingBooks = await pool(select("books", { authorId }));
However, I've never understood why people write this mapping code manually. I believe in code generation tooling as a potential solution for this (where types and maybe a full data access API is auto-generated based on the database schema).
What's the big difference? Why do you like the former but not the latter? What are the characteristics of the former that makes it distinct from the latter?
So maybe building a minimal API, wrapping your SQL queries isn't such a bad idea after all.
I wonder what your experiences had been if after dropping the ORM you had gone one step more and dropped the objects.
After contemplating my distaste for ORMs more carefully, I've come to the realisation that my objections aren't so much to do with the concept of an ORM but rather object orientation itself—and the fetish of treating it as the perfect hammer for every nail.
For the projects I've worked on, I've almost never wanted to turn data into objects. And on the occasions when I've thought otherwise, it usually turns out to be a mistake; de-objectifying can often result in simpler, shorter code with fewer data bugs.
Ultimately, the right answer depends on the nature of your particular business logic, how data flows in your wider ecosystem, and pragmatically, the existing skills of your workforce.
If you need to run a certain query in multiple places, you need to repeat the same ORM expression or refractor it into a function. I find much better to have a module with all my SQL queries as strings. That way whenever I need to run a query I reference it from there. Of course it helps to use meaningful names.
This approach has a lot of advantages over ORMs: * you know exactly what gets executed * automatic DRY code * the names of the SQL queries in the code are self-explanatory and the reader doesn't have to parse the ORM expression every time
Schema definitions are in standalone SQL files, as well as my migrations.
The only disadvantage is that it may be difficult to switch to a different database system, but that is not a problem for us.
Composing SQL expressions using this library instead of using string interpolation/concatenation has several advantages:
* DRY and composition * safety * portability (if you have switch the underlying DBMS)
Often the result is as good or better than my raw SQL. The fact that Python has an amazing REPL makes the process pretty much like testing queries in the database prompt but with less cognitive switch between languages.
In the end it is a matter of taste, but I have to agree with parent posts, SQLAlchemy raises the bar for other ORMs.
But you can't compose them, so there is a lot of duplication. Also, how would you handle dynamic filters and columns? Concatenating strings? That seems error prone. At least a nice query builder would be useful, but then the whole just write sql thing falls apart.
My rule of thumb is that I always go with ORMs for MVPs and small apps. Optimizing for speed usually means going deeper and building a system or queries for yourself. Until that point I usually stick to less verbose code and more to business rules.
Maybe you should not do that? Can you give us an idea of the domain problem you were trying to solve that made you feel the need for that?
A query builder carefully preserves the underlying relational and RPC semantics and exposes all of that to the user in an easier-to-use form. That’s just good cautious modest abstraction.
An ORM believes it knows way better than those dumb RDBs how a database ought to behave by ingeniously pretending that everything you’re dealing with is just nice simple familiar arrays of local native class instances. Which, like all lies, ends up spawning more lies as each one starts to crack under inspection, until the whole rotten pile catastrophically collapses under the weight of its own total bullshit.
And of course it goes without saying which of these approaches our arrogant grandstanding consequence-shirking industry most likes to adopt.
You throw in SQL, provide a simple mapper, done. IMHO it's far superior to ORMs when your database is or may become complicated.
"Any sufficiently complicated program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a decent ORM."
ORMs are hard for a reason. Using an ORM doesn't mean you can't or shouldn't use plain SQL where the situation calls for it. You can mix and match perfectly fine.
[1] https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule Edit: typo.
"Although it may seem trite to say it, Object/Relational Mapping is the Vietnam of Computer Science. It represents a quagmire which starts well, gets more complicated as time passes, and before long entraps its users in a commitment that has no clear demarcation point, no clear win conditions, and no clear exit strategy."
[1] http://blogs.tedneward.com/post/the-vietnam-of-computer-scie...
There was no good reason to be in Vietnam; even taking the stated rationale as a given, which many people did not, it was a concern many levels removed from the actual safety or functioning of American society.
ORMs in contrast achieve much more proximate goals — they solve a real problem and can measurably reduce the amount of code you have to write. It is tedious to write the same sort of SQL query over and over. Even if you prefer to use literal sql for the complex stuff (as I do), ORMs tend to be a significant win (in fewer LOC to write) on abstracting out basic queries.
The real value is to reduce the damage of the SQL language itself — the unnecessarily ordered clauses, the arbitrary inconsistencies in syntax, the worthless parser errors, the lack of any static typechecking — which cause so much code bloat and debug headaches.
There are two reasons to use the ORM: to not learn SQL, and to generate SQL.
The first reason is the commonly provides one, and what leads us into vietnam. The latter is why people try to avoid ORMs, yet find themselves back in vietnam.
What we really need is a less shitty version of SQL.
So instead of building up your sql string in a straightforward fashion, you need to have at minimum an abstraction that delays construction.
You get lead into vietnam as almost a direct result of SQL’s context-sensitive clauses.
Honestly, I think LINQ and entity framework successfully solved most ORM concerns
Anyone got tips on similar frameworks for other languages than java and for other dbs.
In http://p3rl.org/DBIx::Class perl has had such a thing for over a decade now.
It makes me cry that nobody's ever adequately cloned it into other languages. Eventually I'll probably do so myself.
Hopefully that longer answer is a bit clearer than my first attempt.
I think the same. In my spared time I building a relational language (http://tablam.org ..accept more help!) starting even lower: Without a rdbms.
I work in the past with FoxPro, and was possible to build a full app with UI and reports and all stuff you can imagine with a database-oriented language. You code the UI on fox, query with fox, make triggers with fox, etc...
I'm looking in capture the same essence.
Most of the ORM interactions are well formatted code that don’t hide the datastore much. As long as you let the ORM map in objects and it’s performant, life is alright. When the ORM starts running the show there may be no coming back.
What I liked less was all the setup/config ceremony. Compared to ActiveRecord (the Ruby lib not necessarily the orm concept) I was using more LOC before I got to the part where I started saving time on simple queries. I realize this is because sqla uses the datamapper model. But for me thif sweet spot would be auto setup like AR with sql-like syntax of sqla.
Any of the D (Date and Darwen's, not Digital Mars’s) family of relational languages might fit the bill.
> What we really need is a less shitty version of SQL.
My view is the opposite. The power of SQL perpetuates a low-quality software culture. The root issue is a dev culture that can't see past databases.A lot of software design runs like this: (1) translate business patterns into a relational schema; (2) build interactions with that schema; and (3) as that gets harder, use SQL arcana and ORMs and views and stored procedures to squeeze out flexibility.
I worked like this for the first decade of my career. My systems got some use, and struggled along, but they are failed projects.
Repeatedly I had this feeling: the project is almost done, but there are some concurrency issues where I would not even know how to start addressing them.
The database-centric design made it impossible to work past that.
After much searching, I came to this: Stored State is a brittle and unwieldy thing, and you want as little of it in your life as possible. The more you have, the harder you have to work to get anything done. Databases are an institution of Stored State.
As an alternative, you can derive state from messages.
Bingo!
Unfortunately, if people won't even listen to Alan Kay on this stuff, who will they listen to?
Are we talking about distributed store situations where CAP theorem limits apply, or something else?
Why would you optimize for LOC rather than expressing the behavior you want well? In some situations sure—rapid prototyping—but that’s an odd assumption in a general case.
Yes, the object-relational impedance mismatch. It's the classic case of having a hammer (OOP) and trying to make everything look like a nail.
This generation has only ever used ORM's, and so to them those tools must solve some hard problem, they are so complex after all. SQL must be hard.
It turns out that SQL is actually much simpler than ORM's. The failure modes are much simpler, and the implementations more robust. Sure, writing the code can be tedious, but tedeious is not hard. Writing brainless code every once in a while gives you time to reflect on the design of your system, and think about the larger context.
Are they simpler when it comes to matching the data to the application's data structures? One advantage of ORMs is that they encourage this setup from the start.
That depends on the modelling approaches taken by the application developer and DB developer.
And this is an extremely common situation to find yourself in. Code bases which are of middle- to large-sized, and which are inevitably touched by various hands of various skill levels, tends towards complexity.
SQL is the worst source of unmaintainable and difficult to refactor code. It’s difficult to unit test. Error messages are more often than not inscrutable. (I’m looking at you, Oracle.) There is no idiomatic way to break up complex SQL into functions or classes. There’s no type checking. You don’t have the equivalent of Ruby Gems, Python’s pip, or Swift’s CocoaPods. IDE support is limited to syntax highlighting Compare this with something like Eclipse or IntelliJ where you can just Ctrl-click on something and go to the definition. Want to rename a public method in a statically typed language? Pretty easy. Want to rename a column in SQL? Yeah, good luck. Not impossible, but you don’t have any guarantees that something won’t break until runtime.
So yeah. ORM has its place, and modern ones work very well at abstracting away SQL’s weaknesses.
It had a canonical XML format that entities were defined in and code generation for data access layers, domain models, view models etc.
It actually worked better than I've seen the abuse I've seen developers put EF through. I found it nicer and simpler than times I've worked with Hibernate.
I'm not going to say it was perfect, but every time I think back on it it makes me want to revisit the idea of leveraging a bit of code generation or metaprogramming to be able to have a canonical definition of an entity transformed into concerns that deal with the given entity at different points in the application.
That part of it just really hit the sweet spot for me.
Think about it more like MyBatis meets Spring Data JPA. Define that entity in XML the run a code gen which gives you the CrudRepository class but also generates a controller that exposes an API with pretty good good ability to specify adhoc queries. Plus view models.
I think it worked because it both reasonably well designed and hyper opinionated.
I love the idea and it's been fun for me to make demos with, but I've gotten almost zero feedback on the API, and what I have gotten is "can you add a SQL frontend". And yet neither ORM nor SQL are loved
So the question (and I'm not suggesting that Db4j is the answer) is: "what's the API that would be most natural ?"
And that's only necessary when you're trying to manage your data in the application in an object-oriented way. And managing your data in an object-oriented way implies more than just the simple fact of defining classes to serve as data records. Those classes can be entirely equivalent to a struct in a procedural language or a record in a functional one. And I can't remember ever suffering from object/relational impedance mismatch when working in a procedural or functional language. Implying that the spot where you really start getting into trouble is when you trot out some distinctive feature of an object-oriented data model.
I submit that the original sin is treating instances of those data classes as if they are discrete entities that can serve as an application-side proxy for some other discrete entity that exists in the database, almost as if ODBC were just a more REST-flavored alternative to CORBA. Which is a thing that I've often been tempted to do in an object-oriented language, but never in a procedural or functional one.
Which isn't to say that I don't use anything to help with talking to databases in those other styles of language. It's just that I retain SQL as my query language (there are plenty of reasons to do it, none of which I'll bother to repeat here) and rely on a more Dapper-style library to handle unpacking the results into data structures. And I don't really consider those to be ORMs; they're just a special class of data mapping utility library.
So, in conclusion, I think that a more accurate stab would be "Any sufficiently hastily built, object-oriented, database-driven non-ORM program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of a decent ORM."
(1) While treating a single SQL table as a distinct entity is a sin by your argument, I would argue that treating a set of tables as a distinct entity is not. I'm referring to what DDD calls an aggregate, e.g. a customer (address + email history + scoring + whatever tables), or an order (line items, shipping info, payment info, ...) Reading the comments here, I'm starting to wonder if some kind of holy grail or at least useful approach lies in there.
(2) The impedance mismatch has two ends, and one might as well argue that the problem is not mapping tabular data to objects, but mapping objects to tables. This might be an underrated benefit of what is usually called "NoSQL", dwarfed by the whole discussion around "schemaless". Unfortunately, I don't have any experience in that area, but it's something I have long planned on trying. Note that I'm not trying to say that NoSQL "solves the impedance mismatch" but rather that, in DDD's approach to solve persistence, NoSQL might actually be able to do what DDD wants (e.g. with respect to defining a "unit of consistency"), unlike tabular data.
The problem is not in treating entities in the database as entities. It's in treating instances of classes in the application's memory space as local proxies for those entities.
You're on much firmer ground if you understand them as simple pieces of information derived from the state of those entities as of some point in time. Both in terms of ending up with a more internally consistent and robust approach to data (by virtue of removing some temptation to preserve an illusion that this data can be presumed to always be complete and up-to-date), and in terms of not artificially cutting yourself off from most of the power of the relational model.
You're right that this tension is somewhat resolved by just switching to using an object store of some sort. (NoSQL is far too diverse of a subject to treat as a single unit.) But it's a very particular sort of resolution, because it's a sort of least common denominator approach where you just drag the data store down to the level of the programming model. It strikes me as akin to resolving the difficulties in maintaining complex software in Perl by resolving to never let anyone write anything more powerful than a shell or CGI script rather than by looking for a language that will better support you for the long haul. It can certainly be a fine and reasonable choice. The problem comes in when people don't fully realize that that's the choice they're making.
It sucks. Sometimes it sucks less if you map your queries as well, sometimes it sucks less if you stick to SQL and only map your results, sometimes it sucks so much you're better off with a no-sql solution.
But when using a relational database, ORM isn't optional.
Thank you!
This seems like one of those topics where people often feel the need to pick a side for some reason. I've often heard criticism to the effect of, "people only use ORMs because they don't know SQL. Learn SQL!" It's seemingly impossible to convince these people that ORMs are fantastic for reducing boilerplate code and they can coexist right next to raw SQL for problems gnarlier than "select all foo where bar equals baz."
Earlier in my career I made a point to deep dive into SQL. Long story short: I realized that there are a lot of very good reasons to limit the amount of raw SQL in your application that have nothing to do with familiarity.
SQL is just a bad language, and it’s unfortunate that we’re still stuck with it, basically unchanged, decades after its introduction.
For me, it’s almost as anachronistic as COBOL.
I spent a lot of time working in a department that wrote a lot of ad hoc Oracle SQL, for updates to a production system, and complex reports, and there was one guy that was ten times faster than everyone else, and also more likely to get his code correct, and so I paid attention to what he did.
He would break down a complex set of operations into simple individual queries generating temporary tables; he just wouldn't bother fighting with the optimizer, or trying to predict what it would do. So he not only wrote queries that ran quickly, but he wrote them quickly, and he got them correct quickly.
I would read Tom Kyte, exhorting people to use the full complexity of the language and the Oracle optimizer, but from my experience, it just was not the way to go. I wrote many page (or more) long queries that were things of beauty and then found that breaking them down into simple ones actually was usually much faster.
One fundamental thing that I don't think gurus understand, is that for the average grunt in a typical corporation, there is a separation of duties, such that you can't just go and change the things that a system administrator controls. So saying "your database is configured wrong" doesn't address normal life.
Although the pure relational model may be nice to think about, I find it really convenient to have a certain amount of sequential context, and PL/SQL always seemed to me to have a disgruntled relationship with SQL, so I've gradually tended towards Microsoft alternatives.
The argument I've made before when going down the path of ORMs has been: do we forsee needing to use this model code on a different database engine? Outside of simple toy applications, or needing to support different engines with the same code, I agree that ORMs are more trouble than they're worth.
Whenever I get to the point where I'd do something gnarly with a query, I just fall back to SQL statements with results that the ORM can parse; or just dump directly into data structures and go from there, even if the originating tables still have ORMs.
If your framework/library/language doesn't let you skip the ORM - or populate the object with the results of a custom query - well, I'd say that's a failing of that ORM, not of ORMs in general.
You can (and should) use them for simple queries. You have a list of entities you want to query and filter, that's going to be fine. Joins are fine. But if you're doing some complex analysis, an ORM is the wrong tool. That doesn't mean it's a poor abstraction, or difficult to follow, or something to be avoided. It's not the right tool for that job. For the job it's designed for, it's going to save a lot of effort.
SQL is great for analysis -- it's pretty much what it's designed for. But for bringing data into your app and modifying it, SQL is cumbersome and verbose. If you're loading data into objects then you're just creating your own personal ORM anyway.
See digging.
Simple tasks are done maybe thousands of times in any one application. It's the complex tasks are rare.
That's not a fair comparison unless you include the time and effort involved in creating your ORM models, before you can even write those 3 lines of ORM code. The effort to construct those models isn't even fully amortized over your project, since it must be maintained through schema migrations.
ORMs are like putting on an exoskeleton to go buy groceries because it will let you carry your bags more easily. In my opinion, the complexity and indirection they introduce for accomplishing simple tasks do not justify their existence in the vast majority of cases.
It's no more difficult than building tables by hand. There isn't any extra work that you wouldn't also have to do when not using an ORM.
cursor.execute("""
UPDATE employees
INNER JOIN merits ON employees.performance = merits.performance
SET salary = salary + salary * percentage;
""")
Please show me how much simpler and fewer lines that would be in ORM code + model definitions.It's not much more effort to type "create table employees" with all the fixings as it is to type "class employees" with all the fixings.
Depends on the ORM. Just like "raw sql mode", not every ORM supports that. It's not an inherent feature of being an object-relational mapper, it's part of the bells and whistles of some ORM packages. And I'm going to guess you're coming from the Python/scripting universe, because generating models in other languages is definitely more complicated.
But let's pretend that all ORM software can generate model definitions from the database. Just do the update then. I want to validate your argument that a simple update in SQL is "a lot more code especially if wiring up foreign relationships." If you think an ORM is much less code, or simpler, I'd like to see how.
In my API, I have models from my ORM. These can then be used for database migrations, which allows changes in my database structure to be checked in to version control.
From there, these models are obviously used for queries within my app. Generally, you are correct that generating a simple query with the ORM is not meaningfully easier than writing the SQL myself, with the one exception being that the ORM sanitizes inputs to my queries without me having to think about it at all, which is nice.
From there though, I can use the models from my ORM to automatically validate incoming requests to the API endpoints, as well as automatically serialize the results of my queries when I want to send a response back to the client. If you're not building a REST API, obviously this might not be particularly useful, but for someone that is, it has saved me a lot of work.
Finally, generally I find code in my code is a bit easier to debug than strings.
employee = new Employee() { Name = "Bob" }
employee.Manager = new Employee() { Name = "Jill" }
context.Employees.Add(employee)
context.SaveChanges()
This would insert a new employee record for Jill, grab the primary key, then insert the record for Bob with his ManagerId field to Jill's id. If Bob wasn't a new employee, it would perform and update instead. If there were more related elements and different levels of depth the ORM would order the inserts/updates to get the necessary keys and wire everything up. After this block of code, the objects are all updated with their new primary key values. Everything is executed in a single transaction.The ORM is performing operations on objects -- it's pretty much right there in the name -- it's going to be a poor choice for bulk updates because that's not what it's for. But again, nothing stops you from using the right tool for the right job -- whether that be an ORM or raw SQL.
$db->employees->update({
salary => $Q{salary} + $Q{salary} * $Q{merit}{percentage}
});
Not that much simpler in that case, but at least lets you define the JOIN once rather than repeating it per-query.A good SQL access library can validate queries against the database itself at build time.
The Hibernate repo is 700 thousand lines of java code!
There's a tendency amongst some (especially "enterprise" programmers) to forget that dependencies are also just code. If they break, you are on the hook too. You own that complexity.
await client.query(`
update table set c1 = $1, c2 = $2
where id = $3 and userId = $4
`, [c1, c2, id, userId]);And by "drop into", this typically means writing custom stitching code that stitches the SQL cursor results back into the models again. It's rarely straightforward.
The biggest selling point is a massive reduction in boilerplate code. Database agnostism is a feature that almost nobody ever uses, so who cares! Your proposed alternative is engine-specific SQL so you lose either way. At least with an ORM, you'd lose significantly less. You'd just have to deal with the places you used SQL. Which, in my experience, is pretty small and pretty specific.
> this typically means writing custom stitching code that stitches the SQL cursor results back into the models again.
I often feel like people who complain about ORMs have either actually never used one or used a poor one. As long as my query matches the structure of my object(s) I don't need any stitching code. And if they didn't match, I wouldn't write stitching code because that would be waste of time.
If I'm writing a custom query, I'm probably not looking to integrate into the model anyway -- if it's used for reporting I'd just take the results as is. If I'm writing a query specifically to get a matching model object (to manipulate) I'm going to get the whole model and no stitching would be required.
I haven't heard anyone talk seriously about database-agnosticism since the very early 2000s. Maybe some commercial products still try (choose MS or Oracle!), but it's rare nowadays.
The primary selling point of an ORM is that it abstracts marshaling/un-marshaling rows to/from entities. Instantiating and persisting entities to relational storage.
> And by "drop into", this typically means writing custom stitching code that stitches the SQL cursor results back into the models again. It's rarely straightforward.
That's not typical in most uses I've seen. Far more typical are things like:
- Go straight to SQL for reporting, since that's what SQL does. Useful in reporting contexts, and also for list/filter UI screens.
- Use raw SQL to query a list of entity IDs for updating based on some complex criteria. Iterate over the identifiers and perform whatever logic you need to before letting the ORM handle all the persistence concerns.
Do you use the same database engine for your unit and integration testing as you do production? I don't. I use sqlite for unit and local integration testing, and aurora-mysql for production.
As a side note, I quite literally can't use aurora-mysql for local unit and integration testing. It doesn't exist outside AWS.
You can use mysql though...
An integration test that doesn't use the same DB as production is unsatisfactory.
Integration tests should run against a test environment, otherwise, what integration are you testing? I don't see the value in writing integration tests that test the integration between my code and a one-off integration test DB that exists solely for the purpose of integration testing.
Yeah, don't do that.
That's a recipe for tests that don't catch edge cases.
Also if you’re using only the subset of MySQL that is supported by SQLite, you’re missing out on some real optimizations for bulk data loads like “insert into ignore...” and “insert...on duplicate key update...”. Besides that, the behavior of certain queries/data types/constraints are inconsistent between MySQL and every other database in existence.
Finally, you can’t really do performance testing across databases.
1. If the suggested alternative is writing only SQL from the get-go, then why would one even care about database agnosticism?
2. A large part of SQL is compatible between databases, so this might be a non-issue.
3. You can always use specific in-database abstractions such as Views and Stored Procedures for those complicated bits.
Queries look like:
SELECT u FROM ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id
Where ForumUser is your model.
https://www.doctrine-project.org/projects/doctrine-orm/en/2....
[0] https://github.com/martin-georgiev/postgresql-for-doctrine [1] https://www.doctrine-project.org/projects/doctrine-dbal/en/2...
It's the perfect type-safe abstraction on top of raw SQL.
https://github.com/ServiceStack/ServiceStack.OrmLite
https://github.com/CollaboratingPlatypus/PetaPoco
Any errors you get are likely a result of the underlying database/provider (foreign key constraints, etc).
You should never write raw SQL (if possible). You don't need an ORM to achieve that.
With any of the ORMs I've used, as soon as you do something even slightly unorthodox like using a view, you're on your own.
If you're on your own using a view of all the simple shit, you're using an awful and pointless ORM.
In ActiveRecord, there's a method called find_by_sql. You can't call it directly; it's a class method on an ActiveRecord model. So you have to choose which of your ActiveRecord models should be used to instantiate the rows of your result set. (What if your result set doesn't really match any of your models? Pick one arbitrarily.) Your SQL has some extra columns. What happens to the data in those columns? They get monkey-patched onto the individual objects. (Which is stupidly expensive in Ruby.) Other than that, the individual objects are fine. They even have all your smart instance methods, which may or may not behave properly with all the ad-hoc monkey patching.
If you tried to short-circuit all of that nonsense, you tend to get arrays of hash tables. Which is, in my opinion, already a perfectly adequate interface!
I don't want to sound like the ORM defender, but I'm not sure I understand.
This sounds like a deficiency of Ruby and the ActiveRecord record model. In Java, for example, you'd just write a new POJO for your query, which isn't exactly difficult. There are no "smart methods" or whatever.
It is a valid criticism that this can proliferate data classes, but that depends on the application.
results = ActiveRecord::Base.connection.execute(sql)
Yes, both of these things can be solved through disciplined modularisation of ORM logic, but in my personal experience across multiple companies, most developers simply aren’t that disciplined and treat ORM code as any other application code, instead of treating it as the remotely executed database code that it actually is.
In my experience, writing raw SQL (through https://www.hugsql.org/ in my case), you are instead encouraged to think of them as separate and carefully consider the boundaries, which helps keep the queries and application logic modular and allows for more carefully crafted queries that minimise roundtrips and data shuffling.
Again, this has been my experience, across a number of companies. Perhaps your experience differs, in which case, I’m jealous.
B. if it were, that only argues that you don't use the ORM in the latter case.
You can have an ORM in your project and still fall back to plain old SQL if you like. It's not an "either-or" thing.
I wouldn't mind writing queries in SQL so I don't have to learn a new ORM for every language I use. However I like that ORMs unmarshal results for me in objects / tuples / maps of the language sparing me the work.
For complicated things I write SQL and possibly decode the results manually.
If you know a problem is going to be complicated, you know to use SQL.
If you don't know if a problem is going to be complicated, you can default to SQL and decide to use the ORM later if it's a good fit once you've got more problem details.
This is where I disagree. I dislike having a value which is the entity. The basic lesson from relational databases and later data oriented design is that you don't have an entity. All you have are aspects that are related.
All of these questions require deep knowledge of the inner workings of the ORM, when in SQL, this is a lot more straightforward.
The Django ORM takes take a whole of an hour to read, and after that you have 90% of the use cases covered. Considering that I've been using that framework for many years now, the overhead of that knowledge is irrelevant.
This is not a very compelling argument to use ORMs. It is saying "it makes easy things easier". This doesn't really buy you much value. The simple things are already simple. Bringing in a very large, complicated external dependency to make simple things simpler, is not a good idea.
>If you're loading data into objects then you're just creating your own personal ORM anyway.
By this definition, the Postgres driver I use for node is an ORM as it takes a row of string/type identification codes and turns them into javascript objects and javascript types. It isn't an ORM though, that is not what an ORM does. An ORM converts one paradigm into a completely different paradigm, which is why it fails and is a terrible idea.
Unless you’re programming in assembly, everything you do is being converted into another paradigm that is what every compiler and interpreter does.
How does an ORM prevent you from using any SQL features?
When you do the sorts of complicated things that are out of scope for an ORM, you do them in SQL. I don't see any incompatibility.
Back in the day when I was doing C, there were times when we just couldn’t get the speed we needed from the compiler. I wrote inline assembly. Does that mean C was unsuitable because it was incompatible with our performance requirements?
You can theoretically express any standard SQL query in LINQ even though outer joins can be obtuse at first. The translation may not be as optimal as hand written sql, but no compiler can translate code into assembly that would be as optimized as someone who could hand roll their own. We decided decades ago that high level languages were worth the trade off most of the time.
Django, just as an example, does a magnificent job with its built-in ORM. Millions of developers use it, and they are not all fools. A fool is someone who would set out to build a simple-to-intermediate CRUD web app by writing SQL.
> A fool is someone who would set out to build a simple-to-intermediate CRUD web app by writing SQL.
\u{1f644}
SQL works great for simple-to-intermediate CRUD web apps too.
No it makes simple things easy. Simple things in raw SQL are bloody complicated. Even just getting data, manipulating it, and saving it is at least twice as difficult in maintainability and lines of code than using an ORM.
> An ORM converts one paradigm into a completely different paradigm, which is why it fails and is a terrible idea.
I'm not sure where people get the idea that ORMs fail at their job. They really don't. They do it very well and we're all quite happy.
All the anti-ORM arguments here about how ORMs fail at completely different jobs other than mapping objects to RDBMS operations. Well duh. Nobody complains about cars that can't fly and planes that can't fit on the highway but when ORM can't cook bacon it doesn't fit the paradigm.
That has not been my own experience outside of the most trivial queries. Once any amount of complexity is introduced, I find that ORM-based queries often make it difficult to really see whats going on with indexes and locking, I can’t just paste a query (eg to use EXPLAIN) without finding it in a query log first and, unfortunately, in my experience its rare to find teams disciplined enough to not treat ORM code as if it were normal application code (ie don’t mix it into your application logic), so often end up with a few database/application roundtrips, doing filtering in the wrong place etc. Yes that last one isn’t technically the fault of the ORM, but when I see it again and again in real world code, I start to think that most developers don’t have the discipline to be careful with ORM code while when not using ORM’s and writing raw SQL outside of your applications code, you have no choice. Not the ORM’s fault, but still a symptom of using one that I’ve experienced in multiple teams.
Things don't usually start out bad, but they become bad only after you have a more complex codebase with many users (ie many queries). Its at this point that you need to know what a query is doing, yet its at this point that the query logs are full of queries, so finding the ones I want becomes hard. Putting a trace on the queries also becomes hard when you have a large codebase where the query logic is intermingled with application logic. I mentioned this in my comment.
You say it's my "refusal to want to change the way you work that is the issue", but I never chose to work that way. All of the codebases where I've had this issue were inherited: I was not the one to decide to work like this and I did not mix the query logic into the application logic. But that was my point: I've had the same experience across multiple teams in multiple companies, so blaming the developers seems like a cop out and not much of a solution. I'll happily adapt the way I work, but I can't force existing teams to change. Maybe discipline could fix it, but I have not experienced this discipline anywhere I've worked. But sure, its my fault somehow.
Since then I keep using ORMs (because mapping the things you pull from the db to actual objects is undoubtedly good) but I write my queries by hand.
I also dislike ORMs that, by default, when you try to access an object you forgot to pull from the db with the query you wrote, automatically generate another query to pull the data instead of erroring out.
I remember the very first time I had to do anything interactive on a web page. We had a long list of items in a table, and as a stopgap for adding search functionality we were going to sort the table by multiple columns.
Stack Overflow was still a twinkle it Atwood's eye. So I go googling about for stable sort implementations in Javascript and I find plenty. Except they all have the same problem. They were all slow as molasses. I should have taken it as a huge warning that none of these implementations used demos showed sorting of more than 10 items.
I needed to sort 100 items. 300 at the outside. Ultimately I had to go to source material and implement it myself.
Clearly there is some notion in the software community that market for ideas is inexhaustible. That the 'oxygen' in the room is infinite, and therefore I can interject whatever half-assed concept I had into it without costing anybody anything.
Virtually all of us, when we tackle a problem, try to do better than what is already on offer. But what if the thing on offer is truly, horrible? If my only goal is 'better' instead of 'good', then my alternative will be really bad. And in a field full of bad, who wants to be the person who introduces the 6th standard? The 8th?
Keep your dumb ideas to yourself, or put them in question form and ask people why
After someone posted Norvig's Sudoku solver, I was troubled by his statement about how many algorithms he'd have to implement so he just did brute force. So I took a whack at it, got fairly far down the deductive path before things got hard. But I'm not going to show it to everybody. The internet has been working on strategies for 10 years, and they've done way more algorithms than the ones I knew about. The best I've managed is to maybe simplify a couple rules, but I think one could argue that it's the bad definition of 'simple'. It can't handle as many cases, but I can explain it to anyone. Is that enough to add to the noise? Probably not.
For example, Ecto isn't technically an ORM but it lets you do ORM-like things. It's Elixr's data mapping and query language tool. It happens to be one of the nicest "I need to work with data" abstractions I've ever used.
It's a bit more typing than ActiveRecord and even SQLAlchemy, but you feel like you're at a good level of abstraction. It's high enough that you're quite productive but it's low enough that it doesn't feel like a black box.
You get nice benefits of higher level ORMs too such as being able to compose queries, so you can design some pretty compact and readable looking functions, such as:
def eligible_discounts(package_id, code) do
__MODULE__
|> for_package(package_id)
|> with_discount()
|> active()
|> usage_count_less_than_usage_limit()
|> after_starts_at()
|> before_ends_at()
|> maybe_code(code)
end
Each of those function calls is just a tiny bite sized query and in the end it all gets composed into 1 DB query that gets executed.I think Ecto's biggest win was having the idea of changesets, schemas and repos as separate things. It really gives you the best of everything. A way to ensure your data is validated but also flexible enough where you can separate your UI / forms from your underlying database schema. You can even choose not to use a database backend but still leverage other pieces of Ecto like its changesets and schemas, allowing you to do validate and make UIs from any structured data and then plug in / out your data backend (in memory structs or a real DB, etc.).
Initially ORMs can save time when developing as you get an easy mapping between objects and the database.
However in practise ORM tends to give you quite horrible JOINs that quite frankly are hard to understand for humans.
Further more I think that ORM can lead to a bad practice in the sense that you do not need to think about your data layout first. But for database performance data layout is of utter most importance. One need to have data in lay out in a form that makes the application run fast. ORMs does not necessarily provide that. One need to normalize the database.
ORM save you time during initial development but you pay later in the maintenance phase when what is complex queries that humans may not understand are hard to optimize for performance.
Database normalization https://en.wikipedia.org/wiki/Database_normalization https://en.wikipedia.org/wiki/Third_normal_form
And I have, at most, a half dozen difficult queries which require serious optimization, and that optimization isn't defined by the query but by the indexing and storage strategy for the tables in question.
How many of these are a result of of features that have nothing to do with getting data in and out of the database.
Get bare metal. Anything caked on adds friction.
That doesn't mean "write raw SQL". That means I want just enough of an ORM to give me type-safety for my queries and updates. Leave the rest at the door.
Care to provide any examples with comparisons to ANSI SQL or any major SQL platform?
*take note that “SQL became a standard of the American National Standards Institute (ANSI) in 1986, and of the International Organization for Standardization (ISO) in 1987.” the same cannot be said for any ORM.
Same. Hibernate with Spring Data JPA, for me, "just works". But it's not necessarily the right solution for every database interaction. But for a typical microservice which might have up to 20-25 discrete entities, and few if any crazy complex relationships, I find that I plug it in, extend CrudRepository, write some JPQL queries in @Query annotations here and there, and bob's yer uncle.
If you were writing a monolithic CRM system with 900 domain entities, super complex rules governing the inter-relations between them, and trying to write your analytics queries right into the app, then an ORM approach would probably fail miserably.
Simple inexperience. I'd bet most of those people are mid-level developers who have used ORMs enough to hit the rough edges but not enough, or with enough independent agency, to have worked through how to play to ORM's strengths while avoiding their weaknesses. People that were given a hammer and are just understanding that their hammer doesn't work very well to install bolts, and maybe don't have the authority to say maybe I should use a wrench or the flexibility to try this new crescent shaped idea and see if that works better.
You could level the same empty claim at people who like ORMs: I loved ORMs when I was a beginner but learned it's best to avoid them as I accumulated experience. So anyone who likes ORMs is merely in that beginner stage.
I have, at most, a couple dozen "complex" queries in this project.
Whereas I have an order of magnitude more queries that need to be composed from several different query criteria, a task for which SQL is very poorly optimized for and most ORMs excel at.
I have used my ORM for so long that writing a report in SQL or the ORM language is basically the same to me. Neither technology is something I would consider to be "hard", as most of the problems encountered in practice are well-trodden.
Nontheless, a simple ORM query is 20% the length of an equivalent SQL query. And I can compose them trivially. And then I use the model code for the _hard_ part: dealing with the rest of the business logic for template rendering, email sending, API interactions, and so on, for which SQL is completely useless.
Seriously using an ORM is just not hard. Especially if actually do code reviews with your junior team members, which you should always be doing.
And regardless, why should we develop things that only "experienced devs" can work with without blowing a hole in their face? Junior and intermediate devs are often in larger projects.
Thinking about projects I'm aware of.. Stack Overflow created an ORM regardless of if they call it "Micro". They created it and people use it. GitLab uses ActiveRecord.. Gogs and Gitea use ORMs that are a bit fringe TBH.. I actually can't think of a large project I've touched that went completely raw dog on the SQL.
I do believe there are instances where using an ORM, or at least all of an ORM, is not the best choice. But to come out and say they should always be avoided? It's interesting how many people are coming in here SO SURE about that position that is seems completely at odds with what the rest of the industry is, objectively, doing. And when pressed their insights into that opinion are so shallow a baby couldn't drown in them. Hmmm.
There are 500 tutorials of installing Feathers and making a CRUD app, but something as simple as combining results from two requests or doing a raw DB request seems to exist only deep in the docs (and more often than not, requires bonus functionality from an add-on).
I am familiar with managing databases and I can see benefits of ORMs, but I feel like "glue" technologies could benefit from deeper real-world examples that move beyond day 1 tutorials and get into translating existing code to their style. Half the time I am left digging through outdated github code to grasp the basics of how and where mid-level functionality can be used.
https://javarants.com/generate-jpa-or-gorm-classes-from-your...
The second sentence is a little more compatible with the first line, "they can be used to nicely augment working with SQL in a program, but they should not replace it."
That's certainly how I use them. ORMs can save you a lot of irritating typing where it comes to insert and update statements. Aside from that, I write a lot of raw SQL.
I have seen what I would describe as ORM-induced, yaml-induced database damage. This isn't meant as a criticism of these tools per se, they're perfectly compatible with a well designed app. But I have noticed that people sometimes create databases that are useful only in the context of their configuration-file/ORM heavy app. Essentially, the programmers conceive of their data as a set of objects, and they use the config file to store global constants and the ORM to persist files, almost as if they're pickling and retrieving objects back into the system.
The result is a database that can't really be queried with SQL, more or less useless outside the context of the application. I firmly agree with developers who maintain that information will outlive an application, a database will outlive the software that was originally designed to use it (perhaps in parallel with it). I think a SQL database should be useful all on its own as a data source. If you got rid of the app, you'd lose a lot of operations on that data, a lot of UI, a lot of valuable things, but you'd be able to get at and use your data. If that's not the case, I'd seriously reconsider the design.
Kind of hard to do that without understanding SQL, so yeah, definitely learn it.
Not worth it for the small projects nor the large projects. They significantly complicate the development workflow and add another layer of (often times, cumbersome) abstraction between the user and the data.
If you encounter any issues (and better pray you don't), expect to spend hours stepping through reams of highly abstracted byzantine code, all the time feeling guilty when you just want to open a database connection and send the SQL string you developed and tested in minutes.
I've been on a project where we had 20+ tables. After I made the point that the sole purpose of this database was producing json documents through expensive joins that were indexed and searched in Elasticsearch (i.e. this was a simple document db), we simplified it to a handful of tables with basically an id and a json blob; got rid of most of the joins and vastly simplified the process of updating all this with simple transactions and indexing this to elasticsearch with a minimum of joins and selects.
We also ripped out an extremely hard to maintain admin tool that was so tightly coupled to the database that any change to the domain made it more complicated and hard to use because the full madness of the database complexity basically leaked through in the UI.
ORMs don't have to be a problem but they nudge people into doing very sub optimal things. When the domain is simple, the interaction with the database should be simple as well. We're talking a handful of selects and joins and simple insert/update/delete statements for CRUD operations. Writing this manually is tedious but something you do only once on a project. With modern frameworks, you don't end up with more lines of code than you'd generate with an orm. All those silly annotations you litter all over the place to say "this field is also a column" or "this class is really a table" get condensed in nice SQL one liners that are easy to write, test, and maintain.
I don't think good ORMs do any nudging. The issue arises when people assume that because they are using an ORM they don't have to learn the underlying DB. ORMs should be treated as tools that sit on top of your SQL knowledge and allow you to do certain types of things easier.
Like any tool, there are inappropriate uses cases. On one side you have people using an ORM to implement a document store, on the other side you have people that end up hand-rolling a crappy ORM because they thought they didn't need one.
Frameworks for that are awesome and a lot easier to deal with and serializing/deserializing overhead is typically minimal. Columns in databases only have two purposes: indexed columns for querying (ids, dates, names, categories, etc.) with or without some constraints, and raw data (json or for simple structures some primitive values. Some databases even allow you to query the json directly but in my experience this is kind of fiddly to set up and not really worth the trouble. The nice thing is that most domain model changes don't require database schema changes this way because the only thing affected is your json schema. This makes iterating on your domain model a lot easier. You still have to worry about migrations of course.
The added value of using a database is being able to manipulate them safely with transactions and query them efficiently. Bad ORM ends up conflicting with both goals and the added value of well implemented ORM is usually fairly limited. At best you end up with a lot of tables and columns you did not really need mapped to your objects and classes.
A good table structure often makes for a poor domain model and vice versa. The friction you get from the object relational impedance mismatch is best avoided by treating them as two things instead of one. Bad ORM shoves this under the carpet and in my experience does not address this (other than by providing the illusion this is not a problem).
> Columns in databases only have two purposes: indexed columns for querying (ids, dates, names, categories, etc.) with or without some constraints, and raw data (json or for simple structures some primitive values.
You are basically describing a sort of ad-hoc document store with potentially limited ability to query. You've lost many of the benefits provided by a relational DB. Your ability to do run large update queries or reports will be limited (unless your DB provides native json support, which as you said, is fiddly).
If you are going to do this, why not use a NoSQL document store with support for transactions? Then you will get a tool that is designed to work with your use case.
Edit: If you use an ORM, a hybrid approach is possible. Where you store some properties as separate columns and then store the less frequently accessed (or more dynamically structure) data in a json field (which you can deserialize on hydration or on request). The main downside of this hybrid approach is that moving a propery out of the json field into a normal column would require using that fiddly native json support or a fairly slow migration that would need to go through and serialize each json field.
> A good table structure often makes for a poor domain model and vice versa.
Can you clarify what you mean? This has not at all been my experience so I am curious and would love to see some examples.
Regarding the object relational impedance mismatch, check here: https://en.wikipedia.org/wiki/Object-relational_impedance_mi...
In short, there are lots of things you'd do different in an OO domain model vs. properly normalized tables. A lot of what ORMs do is about taking object relations and mapping those to some kind of table structure. You either end up making compromises on your OO design to reduce the number of tables or on the database side to end up with way too many tables and joins (basically most uses of ORM I've encountered in the wild).
For reporting, you can of course choose to go for a hybrid document/column based approach. I've done that. In a pinch you can even extract some data from the json in an sql query using whatever built in functions the database provides. Kind of tedious and ugly but I've done it.
Or you can use something that actually was built to do reporting properly. I do a lot of stuff in Elasticsearch with aggregations and it kind of blows most sql databases out of the water for this kind of stuff if you know what you are doing. In a pinch, I can do some sql queries and I've also used things like amazon athena (against json or csv in s3 buckets) as well. Awesome stuff but limited. Either way, if that's a requirement, I'd optimize the database schema for it.
But for the kind of stuff people end up doing where they have an employee and customer class that are both persons that have addresses and a lot of stuff that is basically only ever going to be fetched by person id and never queried on, I'll take a document approach every time vs. doing joins between a dozen tables. I also like to denormalize things into documents. Having a category table and then linking categories by id is a common pattern in relational databases. Or you can just decide that the category id is a string that contains some kind of urn or string representation of the category and put those directly in in a column or in the json. You lose the referential integrity check on the foreign key of course; but then you should not rely on your database to do input validation so that check would be kind of redundant.
um... what? Are you meaning to say that Postgres does a pretty good job as a document store? (not synonymous with "nosql")
Despite that that wikipedia article says, most (if not all) of the "impedence mismatches" described apply to most document stores as well. I would be curious to hear which of the mismatches described in that article you think are avoided by using Postgres as a document store. In my mind, the reason for using a document store is to have flexibility in the structure of your data (which can be a positive or negative depending on your needs).
> Or you can use something that actually was built to do reporting properly. I do a lot of stuff in Elasticsearch...
Of course there are document stores with good reporting. I was talking specifically about the downside of using Postgres as a document store given your complaints about its native json support being fiddly.
> But for the kind of stuff people end up doing where they have an employee and customer class that are both persons that have addresses and a lot of stuff that is basically only ever going to be fetched by person id and never queried on, I'll take a document approach every time vs. doing joins between a dozen tables. I also like to denormalize things into documents.
I often de-normalize addresses in my tables, but that choice is based on how you will want to store and update that data. A separate address table is good if you want to be able to automatically propagate address edits between records. A de-normalized address is good if you want keep records of that address for the purpose for which it was used. De-normalization is always an option with a relational DB, but normalization is not always easy some document stores.
> Having a category table and then linking categories by id is a common pattern in relational databases. Or you can just decide that the category id is a string that contains some kind of urn or string representation of the category and put those directly in in a column or in the json. You lose the referential integrity check on the foreign key of course; but then you should not rely on your database to do input validation so that check would be kind of redundant.
I'm not quite sure what you are on about here. You can use constraints on columns that are strings and you can have tables that are composed entirely of an indexed string column to point that constraint towards. Integer Ids are primarily used just to save space. (I don't really see how this is relevant.)
I don't see anything here to justify your assertion:
> A good table structure often makes for a poor domain model and vice versa. The friction you get from the object relational impedance mismatch is best avoided by treating them as two things instead of one.
To be frank, it sounds to me like you ran across a bunch of poorly designed DB schemas (or schemas you didn't understand the design decisions for) and decided that it must be impossible to design good DB schemas and so you just use unstructured document stores instead.
I understand both points of view, with ORM saving my brain from writing massive JOINs on multiple tables yet at times forcing me to dissect a ORM query because it's doing something stupid. But if I didn't use ORM I would have probably not even made those stupidly complicated tables that I have to debug in the first place. Maybe even worse, is that instead of learning SQL I had spent all my time learning the ORM's API.
It's a balancing act, with good points on each side. I like writing my own SQL, I think it makes me think harder what I'm doing. Sure then I'll be probably writing my own helpers that might resemble a half-assed ORM but as long as it is kept simple, outside of the hands of those who wish to over-abstract everything with their fancy design patterns, it should be highly efficient and easy to understand. And the best of all, my understanding of SQL will be a lot more useful than knowing some language-specific ORM.
Intermediate programmer: ORMs just get in the way! SQL isn't that hard after all.
Advanced programmer: I write a lot of SQL, but I use ORMs to cut out most of the boilerplate.
The world is full of tools that experts use because they understand what the tool is doing, and why this is a good thing... most of the time.
Having the tool is a poor substitute for the knowledge that led you to use the tool instead of doing it by hand. There are times where you would not do a thing by hand and so you don't ask the tool to do it, and there are times you ask the tool to do something you would never do by hand.
Super-advanced programmer: allowing my database structure to be influenced by the needs of an off-the-shelf ORM will make it worse.
My working principle is to have data spend as little time as possible being thrown around within application code. I tend to find that the longer data spends being sieved through layers and tossed around inside your application, the more data bugs you'll end up having.
And when it comes time to display data to the user, it's rarely inconvenient to write an SQL query that fetches exactly what you want to display in exactly the right format and exactly the right order—obviating the need to have any "objects" that "understand" your data model.
The problem is that far too few programmers realise how deep the SQL rabbit hole goes; it's treated like a little side-hustle like regular expressions, when for so many programmers it's the most valuable skill to level up.
Also, the ORM allows me to specify models that are not only used for structuring the database, but also for validation of incoming JSON requests and easily serialize queries back to JSON.
Hibernate and JPA encourage designing your domain classes first and then generate the DDL from that.
> Also, the ORM allows me to specify models that are not only used for structuring the database, but also for validation of incoming JSON requests and easily serialize queries back to JSON.
Postgres has great JSON support, does the ORM something with JSON that Postgres cannot do?
This is not true. There's a culture of doing that in demos, but every production shop I've ever been in curates DDL by hand. Flyway is pretty popular.
This is a feature, not requirement or need of the ORM. It seems pretty silly to let the existence of a feature prevent you from making designing the structure of your DB correctly.
> does the ORM something with JSON that Postgres cannot do?
Postgres's json functionality is used for manipulating and querying data stored in the DB.
I believe the poster is talking about deserializing and validating json from REST requests and serializing json for REST responses using the mapping defined for the ORM.
These are also things that the json functionality of Postgres can do. For example, look at to_json and json_agg.
A couple other things I've learned:
* Never re-use complex types in both your API and your schema. These things evolve at different paces and you should never have to worry that a change to your schema will break an API (or vice-versa). The minimal extra typing to have dedicated API types is well worth it.
* Storing untrusted client-submitted JSON in your database is a terrible idea. This is a great attack surface, either by DOSing your system with large blobs or by guessing keys that might have meaning in the future.
import json
data_from_json_request = json.loads(*request body*)
> easily serialize queries back to JSON import pymysql
import json
conn = pymysql.connect(*connection details*,
cursorclass = pymysql.cursors.DictCursor)
cur = conn.cursor()
cur.execute(*query*)
json_query_result = json.dumps(cur.fetchall())
I don't feel that a ORM is better than this personally. I know exactly what this is doing at all times. No magic, no guess work about the philosophy of the software. This is probably faster as well.It does, if you want it to, with several typecheckers available.
For example if I'm going to use a value in a query, because I'm using parameterized queries the type conversion to string happens implicitly so type doesn't actually matter. If I get 2 or '2' it all ends up as '2' and the database infers type by the column type.
If I need something to be a integer and I don't trust the upstream system then you have to:
int(*number*)
At the end of the day if my JSON is going back to JavaScript I can't trust types either so I have to take the same precautions.On the other hand services that aren't actively utilizing data I write to be fairly agnostic about that data. "be conservative in what you do, be liberal in what you accept from others." is sort of how I aim.
@Path("/things/{thingId}/tags")
public class ThingTagsResource {
@PUT
@Transactional
public Thing setTags(final @PathParm("thingId") long thingId, final SortedSet<String> tags) {
final Thing thing = dao().load(Thing.class, thingId);
thing.setTags(tags);
return thing;
}
}
I think this code hews much closer to the programmer's intention, providing essential input validation with minimal boilerplate. It's also comparatively easy to test.The former almost invariably spurt out inefficient queries, or too many queries, or both. They usually require you to let the ORM generate tables. If you just want to have your object oriented design persist in a database, that's great.
The latter almost invariably results in trying to reinvent the SQL syntax in a quasi-language-native, quasi-database-agnostic way. They almost never manage to replicate more than a quarter of the power of real SQL, and in order to do anything non-trivial (or have things done in a way that lets your database server scale) they force you to become an expert SQL anyway, PLUS an expert in how your ORM translates its own syntax into SQL.
And once you become more expert at SQL than your ORM, it's not long before you find the ORM is a net loss to productivity—in particular by how it encourages you to write too much data manipulation logic in code rather than directly in the database.
All an ORM needs is a mapping between database fields and object properties so a good ORM should allow you to separately define a mapping between your object model and relational model so you retain full control of both.
> it encourages you to write too much data manipulation logic in code rather than directly in the database
I find doing too much business logic related data manipulation directly via SQL to be an anti-pattern that creates significant problems with testing and separation of concerns.
ORMs are good at hydrating objects and persisting updates to those objects. Hand writing code to do this is a waste of time.
SQL is good at running reports and performing mass updates an ORM that doesn't allow you to easily do this is bad.
My model of thinking is that any copy of data that isn't currently resting in the database is potentially stale; avoid round trips like the plague; get new data into the database as soon as possible.
For me and the way I work, it's less about good vs bad ORMs, rather more often a question of whether I even want my data hydrated into a special object at all. I've come to the realisation that for the kind of work I do, data objects almost always end up being an unnecessary layer of indirection that don't give me any real benefits—and they change the way you think, because every transform becomes an opportunity to write a method on an object and not a straightforward query.
Nothing about an ORM stops you from persisting data as soon as it is ready or updating the state from the DB to ensure consistency (or from using transactions).
> rather more often a question of whether I even want my data hydrated into a special object at all. I've come to the realisation that for the kind of work I do, data objects almost always end up being an unnecessary layer of indirection that don't give me any real benefits
Yeah, if you don't need to use objects than there is no reason to use an ORM. Knowing the right tool for the job is critical and Objects and ORMs are not infrequently used when they are not needed.
In my work, updates are rarely atomic and business logic is complicated and intricate. It is extremely hard know what data you will actually need and so it makes sense to pass around a complicated object that has all the potentially needed state. This also gives me the option to separate logic about when to commit/rollback from logic about what to persist.
> because every transform becomes an opportunity to write a method on an object and not a straightforward query.
For me, this is a plus, not a minus :). Methods are easier to test and re-use as part of a complicated business logic flows. They make it easier for me to manage when data gets synced with the DB without having to duplicate code.
> —and they change the way you think,
I am going to pay more attention to this and see where I may have made mistaken presumptions and used objects unnecessarily when I could use atomic updates or queries instead.
> I am going to pay more attention to this
Everyone thinks about things in their own way I suppose, but perhaps a way to parse it could be to think about whether you're approaching data from a "load and store" mentality or a "truth and snapshot" mentality.
In my mind, unless you wrap the entire programming round trip in a transaction, all data sitting in variables are a snapshot of the past and thus stale by definition.
... or both. Both is always a possibility. Welcome to programming.
... or both. Both is always a possibility. Welcome to databases.
Thanks for the condescension though.
(As for the condescension, I agree with that too. It was aimed squarely at the GP in the marginal hope that he gets to experience his own tone mirrored back at himself. It might just offer him some insights into perspective.)
Of course, it's the internet, so dry british cynicism and condescension aren't as trivially distinguishable as one might hope. Sorry my tone didn't come across correctly.
I agree that most ORMs are shit, and if you want to make specific complaints, I'll probably agree with most of them.
But if the choice of ORM is forcing you to design your database to its limitations, you should really be asking yourself whether it's time to switch to a different ORM.
Why does your database structure have to be structured by your ORM?
The only ORM that I have used are LINQ based ones and they can model any database relationship.
Don’t get me wrong, my first instinct when starting a project is to use Dapper - a Micro ORM written by Stack Overflow that just maps a sql query result to object and doesn’t generate sql.
I've approached tons of problems by making it work in SQL first and then translating it into AR afterwards (to some degree or other). I would reject an ORM without an "escape hatch", but AR is wonderful for taking away the boilerplate while still letting you write SQL when you need it, and even letting you make SQL more composable by defining scopes.
I'm just wrapping up a couple C# projects where everything is direct SQL, and oh man is it verbose and painstaking! Every day I long for more Rails work. :-)
It's the perfect micro-ORM. Eliminates boilerplate, gets out of the way otherwise.
Not in the traditional sense.
> And yes, they do remove a lot of boilerplate.
Here is all the "boiler plate" you'd need to use something like OrmLite with C#.
Type safety. No boiler plate. No abstractions. Errors are a result of the underlying data storage.
This is where it's at. The sweet spot.
high five
But I certainly hated writing boilerplate. So I often used Excel and/or SQL to write my SQL.
And I should add that I was using SQL for data forensics, in a very ad hoc way.
Edit: Now I use Calc and bash to write my bash ;)
Of course, your database may come with features that can help too (views, udfs, etc)
I'd generally only reach for an ORM in one circumstance - when my team already knows it well, and can move fast with it. It should also be popular, so that its likely new team members already know it, and can move fast with it. Otherwise, you're just putting unnecessary obstacles in front of your team, in most cases.
The other extreme from using an ORM "for everything" is using SQL "for everything", either via loads of handwritten ad hoc SQL, or stored procedures, UDFs, views, or a mix of all of these. This is just a different nightmare. And don't be fooled: it really is still a nightmare.
A sensible approach blends use of an ORM with handwritten SQL where needed. In fact most ORMs will allow you to do things like build collections of objects from custom SQL anyway, so there's really no need to shy away from it.
One other thing I'd say: I wouldn't necessarily trust my ORM to adequately design my database for me via a code first approach. It's more work but thinking about the data model and explicitly designing the database often yields better results, and you have more control. Code first is OK for simple stuff, but often even simple stuff becomes complex over time so I tend to shy away from it.
This is really where it's at. Seriously people. Nobody should be writing raw SQL.
Give me just enough of an ORM/abstraction to give me type-safety, leave the rest at the door.
What?!? That’s an absolutely foolish and naive assertion. Please don’t give any of your fellow junior devs that “advice”...
Unless you think I'm suggesting that no devs should ever write SQL at all in their career? Which isn't the case.
Not a bunch, just the large batch queries and reports that use complicated joins. There a places where it makes sense to take the trade-off between maintainability and performance.
> Unless you think I'm suggesting that no devs should ever write SQL at all in their career? Which isn't the case.
It sure appears to be what you are suggesting. Perhaps you should clarify what you were trying to say?
I think nobody should be writing comments here. They should be generated by some tool. So, please, be serious.
:)
Business objects need a lot more, from rendering, processing, email sending, notifications, etc.
All those things are well approximated by an object and poorly approximated by SQL.
OOP's strengths surround modeling nested object structures and encapsulating state and logic within opaque containers. If you're primarily writing data to/reading data from a relational database, everything gets flattened and exposed. So you have to ask yourself what you're getting from it at that point.
Rendering and processing are well-suited to FP. "Email sending" and "notifications" are vague, but I see nothing about SQL + FP that makes those harder.
Of course. But when you operate on it. When you end user is typing into a screen. When you're validating the contents. You're doing in the application, not in the database. RAM is where the data lives when it's being acted on.
It's like saying your Word processor should have no internal data structures different from how the file stored on disk.
OOP is a great way to model data sotred in RAM and operated on. And an ORM is a great tool for persisting that structure to disk as needed.
2016: https://news.ycombinator.com/item?id=11981045
Discussed at the time: https://news.ycombinator.com/item?id=8133835
Both sides continue to distrust the other, though.
Writing your own SQL migrations is actually incredibly straightforward if you just think for a few minutes about how you would do it if ORMs didn't exist. Many database systems have ways to store metadata like schema versions outside the scope of any table structure, so you can leverage these really easily in your scripts/migration logic. EF6 uses an explicit migration table which I was never really a huge fan of.
One thing we did do that dramatically eased the pain of writing SQL was to use JSON serialization for containing most of our complex, rapidly-shifting business models, and storing those alongside a metadata row for each instance. Our project would be absolutely infeasible for us today if it weren't for this one little trick. Deserializing a JSON blob to/from a column into/from a model containing 1000+ properties in complex nested hierarchies is infinitely faster than trying to build up a query that would accomplish the same if explicit database columns existed for each property across the many tables.
> complex, rapidly-shifting business models
Your business logic classes have 1000+ properties. And you plan to not migrate them when the schema changes but leave many instances with old versions of the schema sitting in the datastore. Your application logic is going to get nasty!
Its difficult for me to picture how Dapper even comes into play when you're doing this trick with the JSON blob.
Why not use NoSQL?
Also 1000+ properties on an object? I know some domains sometimes surface these kind of extreme cases, but is there not an alternative to having 1000 properties in one object?
The relational concern is the storage of metadata sufficient to locate & retrieve the state. E.g.: integer primary key, name of process, current transition in process, some datetime info, active session id, last user, etc.
The 'non-relational' portion is simply a final 'Json' column per row that contains the actual serialized state. This state model can vary wildly depending on the particular process in play, so by having it serialized we can get a lot of reuse potential out of our solution.
In terms of the models, it's not 1 gigantic class with 1000 properties. Its more like a set of 100+ related models with 5-30 properties each.
It sounds like a workflow engine. I'm picturing one table that is very generic that tracks "this job id, this workflow type, this stage in workflow, this entity, this state of the entity" and a single job has multiple of those entries over the lifetime of that job execution and the JSON blob is the current state of things, so that you don't have to go and recompute that.
Yeah, it seems like a reasonable choice.
What sticks out to me in a scenario like that is a good CQRS implementation. The write side of it pumps in the history of the job execution, then denormalizers run to project that into a shape amenable to being read by the application.
I’ve done it. For MLS syncing software, there are lots of properties, not thousands but can be hundreds. And each MLS RETS has its own Schema, so for any kind of logic portability this is necessary.
Actually, storing the raw data as a blob is a flexibility technique and is a separate concern than the number of fields. As I can’t predict the future set of optimized queries I’ll need, and I don’t want to constantly sync and resync (some MLS will rate limit you), then this way I can store the raw data once, and parse plus update my tables/indexes very quickly.
I'm unsure if it's better than the typical way of modeling EAV though, I'd have to think it through.
There are so many "gotchas" to using EF that I'm not sure if was worth using to begin with (for a complex project at least). 8 years into developing and maintaining a huge ecommerce platform built on EF I think if I started over I would probably not use EF.
In both cases, one is a higher level abstraction over the lower level capabilities, which can provide a quite large gain in usability and ability to easily understand what is going on at the level you are working at, for the loss of hand optimizing at a low level to get just what you want in every case.
Similarly to with Assembly and C, you can often drop to the lower level as needed for speed or other very specific needs.
In both cases a good understanding of the lower level language will help you both know when it's appropriate to drop to a lower level for performance or for a special feature, and when it doesn't matter because the ops/SQL generated is rear optimal anyways or the gains are almost definitely less than the problems caused from a maintainability perspective.
I'm perfectly happy to use an ORM for 95% of my DB needs. Just the query builders that generally ship with them are worth the price of their inclusion IMO (at least for the good ones), as it can greatly simplify queries that are variable based on different parameters you may have per run.
There is some information on Internet on query optimizations, look it up. There are also server side optimizations, specific to the RDBMS you use and the flexibility it has.
I don’t think anyone would suggest using an ORM for reporting, aggregating, etc. I would even go so far as suggesting using a different type of database/table structure - a columnar store and send data to a reporting database. A reporting query over millions of rows wouldn’t be real time anyway.
For aggregation and reporting my usual go to would be Redshift - a custom AWS version of Postgres that uses columnar storage instead of rows and/or something like Kinesis (aka similar to Kafka) for streaming data and real-time aggregation while the data is being captured.
For the other 5% of the time, you’re just building a todo app, use whatever fancy general purpose libraries are hot at the moment.
Since we are in the world of analogies, using an ORM is like taking a shovel and using it as a prop (without speaking) to explain excavator operator where to dig, how deep, how wide, what things to avoid etc. Except querying a database can be much more complicated. You might be successful with some simple tasks, but it will fall flat on more advanced things.
Anyway perhaps if databases would expose an interface and allowed you to directly write a query plan then that wouldn't be so bad? But then many people would complain that you need to know a lot ot use it, and also based on data your plan for the same data might need to be different to be fast.
Whereas, the mini-ex is a complex hydraulic machine requiring regular maintenance and an operator that knows how to use the controls, and a fuel source. Lots of dependencies.
There was a youtube video of a group of men using shovels cooperatively to ‘throw’ cement:
var males = from p in context.Person where p.Sex = “M” select c;
Any more imperative whether context represents a C# object or a database?The mindset was correctness and validity of a detailed and interrelated collection of known facts, at rest on disk.
Merging 2 of these, say when an insurance Corp buys a competitor, took detailed and painstaking effort by people with both the domain and systems knowledge.
It's very different now,with Json doc stores, where in 5 years time noone will know what timezone that happened in, or if this person is that person with different name because of lossy utf8->ASCII .
Comparing SQL to Assembly is probably one of the worst comparisons you can make. SQL is a very high-level and powerful declarative language that I would compare, if at all, with functional languages and not with assembly. A few lines of SQL are counting, sorting, grouping, aggregating, and merging data that would take dozens of lines if written row by row in a procedural language.
A better example would be having someone write C macros for you to implement your logic in. Like you can't call `open` to a get a file handle, you need to use the `WRITE_TO_ABSOLUTE_FILEPATH(path, content)` macro every time you wish to write something to a file because someone thought "you know, C is complicated and what are we gonna do, rewrite all uses of open every time we might want to use a different flag".
Comes with its own limitations while being sufficient for _some_ people's use cases.
pointing out that the comparison is not perfect does not invalidate or refute anything. Every analogy, by definition, is not perfect. If the analogy were to ever become perfect, it would stop being an analogy and would instead be a tautology.
"an ORM is like an ORM".
IOW, the differences are WHY analogies are useful.
An analogy cannot be wrong because it is not comparing anything. it's communicating an idea.
ORMs generally serve some subset of three purposes: 1) constrain the dynamic nature and expressiveness of SQL in such a way that it can work well in less expressive languages 2) serve as a bridge between a typed language and an untyped language and 3) a way to avoid your team needing to learn and work in two languages for backend dev (similar to arguments of using Node so that your frontend and backend can be the same language).
To claim one is superior to the other without context is just a very inexperienced statement to make. I'd highly encourage any team that is comfortable being a polyglot to consider avoiding ORMs and embracing the DB as much as possible.
It's not about learning or working in multiple languages, it's about duplicating the specification of important details.
The holy grail would allow me to write critical business rules in one place, then make use of them everywhere. I want to only have to specify once that `email` is required on `contact`, and have that manifest as `NOT NULL` on the database, an API validation, and a little star next to the field on the UI. Unfortunately, there doesn't yet seem to be a solution to that problem.
That was one of the most interesting things to me about the Drizzle MySQL fork project. One of the goals was to make the SQL language that was used a plugin, so you could use just about whatever you want, JavaScript, Perl, Python, Ruby, Haskell, whatever, as the SQL server level. Being able to share client and server level code opens up some interesting possibilities. Alas, I think it ended up being abandoned.
Everyone should know sql, but choose an ORM (micro-ORM) that doesn't have any abstractions, and gets data in and out. There are many solutions that offer you this without needing to write any SQL.
https://github.com/CollaboratingPlatypus/PetaPoco
https://github.com/ServiceStack/ServiceStack.OrmLite
Micro ORMs is where it's at. You're asking for trouble if you choose something that does WAY more than it should, like Entity Framework.
The first item about querying is relevant but all ORMs allow you to drop into SQL to execute a complex reporting-style query. It's not really necessary if you are just querying in objects to manipulate (which is what ORMs are good for).
But I do agree that data mappers tend to be better.
For example, given the small django program:
dbcall = models.Purchases.objects.filter(amount = 100)
if dbcall:
do_something()
if len(dbcall) > 5:
do_something_else()
third_thing(dbcall[0])
Does the above app make 1, 2 or 3 database requests? There is an answer, but it's not at all clear to the developer.A cursory reading of the documentation is usually a first step in acclimating to an otherwise unfamiliar system.
It just so happens, for your example here, a complete explanation[1] can be found at the very top of what's likely the most vital subsystem's documentation. During due diligence, this information will be among the first encounters.
[1]https://docs.djangoproject.com/en/stable/ref/models/queryset...
The "very top" of the django lazy evaluation documentation you pointed to hardly sheds much light on the issue. The documentation just says that certain operations cause database queries, and that some are cached. It's not clear whether doing two similar operations consecutively will result in two database queries (e.g., checking len(dbquery) twice, or checking for truthiness then pulling the first object from a database query).
The django documentation does differentiate between evaluated and non-evaluated database queries, but to the developer, it's the same object. Would be much better if an "unevaluated query" and an "evaluated query" were two different types with different semantics.
Worse, these lazy evaluation semantics are not terribly pythonic. Checking the length or truthiness of a string, list, set, dict, or tuple is an O(1) operation. Checking the length of a database query may (or may not) make network calls, and may have O(N) performance or worse.
Databases queries can give rise to performance bottlenecks and race conditions, and hiding what's actually happening from the developer is a recipe for all sorts of problems.
It looks like it parses the schema to find type information (2 minute skim, so please forgive me if I got it wrong!) Hoe does it handle schema changes?
An ORM - as the acronym says - is helpful to map database records to objects in the system. The meaning of the acronym already says that an ORM is not really designed for scenarios like aggregations and reporting. Within those contexts, you don't normally reason in terms of list of "objects" and "relationships" between them.
A "SQL builder" gives you a nice programming interface to build and manipulate SQL statements. Manually building complicated SQL strings is tedious, error prone and it makes it hard to reuse the same queries. With a SQL builder instead you can easily add dynamic conditions, joins etc, based on the logic of your application. Think of building a filterable Rest API that needs to support custom fields and operators passed through the URL querystring: concatenating strings would be hard to scale in terms of complexity. Some people prefers to use templates instead of SQL builder to add conditions, dynamic values, select fields etc. I personally find that this approach is like a crippled version of a proper SQL builder interface. I prefer to use the expressiveness of a real programming language instead of some (awkward?) template engine syntax.
I think the confusion between these two different tools is caused by the fact that in some popular frameworks as Django or Rails you just get to use the ORM, even if behind the scenes the ORM uses some internal query builder.
Other ORMs like SQLAlchemy instead gives you both tools. You can indeed use SQLAlchemy as a ORM and you can also use it directly as a SQL builder when the ORM abstraction doesn't really work.
Normally, if someone tells me that it's better to write SQL queries by concatenating strings, I'd ask them how they'd build a webpage that filters products in the catalog with a series of filters specified by the user (by price, by title, by reviews, sorting, etc.). Try and build that concatenating raw SQL bits, without making a huge mess.
Also, the "just learn SQL" may apply to ORMs, but certainly not to a SQL builder.
With a good query builder in hand - it is very unclear to me why anyone would ever want to use an orm.
A few weeks of suffering through DIY SQL is really the only way to fundamentally understand how the database is working for (or against) you. Once you learn it, it really does become like a second language. You can fade in and out of proficiency based on recency of exposure, but it is mostly like riding a bicycle. One other advantage is you can take it across any SQL platform without a second thought. Many ORMs have provider-specific compatibility woes to contend with.
My process is generally - write SQL to find the best query, then convert the SQL to Knex for integration.
As a simple example, say you are making a simple query to the db, but in some edge cases you need another column to be selected. Knex lets you do this:
let query = KNEX("table").select("col_1")
if(weirdCondition) {
query = query.select("col_2")
}
const rows = await query
Which naturally compiles into either SELECT col_1 FROM table
or SELECT col_1, col_2 FROM tableI don't really like it much, but it did make the API service super thin, and the "database guys" were happy to do it all in SQL. Of course, testing that beast isn't so fun, and there be dragons...
In the end, I tend to favor creating APIs with scripted languages that require less translation to work with the database side, and structure responses to match the expected data layouts for the API side. With node, I usually create/use a simple abstraction...
var result = await db.query`SELECT ... WHERE foo=${bar}`;
or
var result = await db.exec('sprocname', {...});
In either case, not really a need for a formal ORM here.Does anyone know how such a paradigm came to exist? What problem is this solving?
It's absurdly overkill if you're just doing CRUD, but if the DB uses some crazy schema that doesn't match the usage it keeps you sane.
One such project I worked in had a primitive form of event sourcing built in. Everything was logged and could be replayed. It was kinda neat.
https://www.vertabelo.com/blog/business-logic-in-the-databas...
https://www.martinfowler.com/articles/dblogic.html
There is also this video on how to properly architect these kinds of applications: https://youtu.be/PMPW024NIDE?t=1052
Its query builder is super transparent, consistent and flexible, thanks to LINQ, but still generates very performant queries.
How I use ORM is to help automating some of the things, while on top of that, I'd still write raw queries for the more complex things. For example, I create base tables using ORM, and then lay off all the joins and more complexity in a handwritten views. For query, I use ORM for something simple such as direct single-table queries or straight forward joins. When things get more complex such as joining a bunch of tables, or doing nested queries, I just use raw query.
The thing about ORM is that it should always be used as a complementary tool. Feel free to mix and match it with raw SQL queries as needed. Trying to do everything in ORM is just too much. One would end up spending too much time learning about the inner working of the ORM, instead of getting things done. So yeah, use it as a complement to raw queries, not as a substitution.
2. “Executing it”—this is called a SQL client library. If you install your SQL statements as stored procedures you can execute those procedures by name, for example. Not that hard.
3. “Bringing the data it returns into the object oriented universe”: I am typically satisfied with an array of hash tables. Funny story: what do you get when you use ActiveRecord’s find_by_sql with a query that returns columns not in the original table? You get a collection of objects each with the extra column monkey-patched to the individual object. In other words, a slower and stupider hash table that happens to have dot syntax rather than bracket syntax and a bunch of do-they-even-still-work-in-this-context instance methods attached.
It is, and all ORMs I know use it under-the-hood. It is the boilerplate code by which SQL is issued to the DB, and results parsed into something usable by the rest of the program that people try to avoid writing over and over.
- they push developers to design super-normalized db schemas that look beautiful on paper but are horrible in practice
- the average developer has a very superficial knowledge of how ORMs work and this often leads to bad code/performance
- soon or later you will find yourself fighting the "framework" because what you are trying to do does not fit their model (e.g: upsert)
In my experience, this whole idea of abstracting from the DB is faulty at its root. You want your code to be close to the DB so that you can use all the greatest and latest functionalities without waiting for the framework X to support it.
I have found that JOOQ or simply Spring JdbcTemplate in most cases are more than enough.
I'm definitely guilty of over-normalizing at least some of the time, but I'm less convinced that it's related to ORMs so much as it is to frequently having to build things without knowing how related they'll end up being in the future.
Generally ORMs try to abstract away the database entirely. This is mostly fine for CRUD stuff where you really just want to persistently stash away something basic off host. If you could write perfect uncrashing programs you'd probably just keep them in memory.
As soon as you need to find things, especially based on their relationships, you'll need to be aware of what indexes you have available at least. Eventually you'll want to have control of the precise query
JOOQ lets you compose queries programmatically without really hindering you from producing any query you want but not forcing you to glue strings together either.
I think it's easier to learn SQL than learn an abstraction of a DSL.
My initial design is always done in the database. Whether it's a little feature or a green field new project on a blank sheet of paper, that sheet of paper is the Schema Designer of my db (or a schema.sql if I'm in postge/mysql land).
Once the schema is nailed down, the object structure flows out easily. You can follow foreign keys, many-to-many tables and non-identity primary keys to figure out your children, relations, and inheritance. It's so well defined that for my own stuff I just point my code generator at it to get a pile of base classes and stored procedures for all the CRUD. (And re-run it at build time to ensure that everything always matches up.)
So when it comes to pulling down records, modifying them, saving them, grabbing sets of them to spin through, etc. There's never any mismatch because what you get from the db will naturally look just like what you need. Because you designed it to be that way.
I may hazard a guess as to why so many people do run into issues, and it's because I notice that nearly every ORM I've seen in the wild expects you to define your schema someplace other than the database. It'll have some wacky XML config file that you're supposed to keep up to date, with even wackier "migrations" for when that changes. And it'll then either build your db schema for you or expect you to have something in place that matches what it wants.
But that's silly.
There's already a perfectly good place to keep your schema. In the database.
And I guess it follows that if you don't design your system with a sensible relational data model in mind, you might find that your object structure doesn't in fact fit in a database correctly. Could it be that that's what people are describing when they talk about Impedance Mismatch?
Then simply build your project and fix any compile-time errors that arrived when the base classes were all blown away and rewritten.
Extra points for keeping your column names in string enumerations so you can't ever get runtime errors from typos or having renamed a column. (all handled by the code generator, of course)
But yeah, even on my mature projects I still find myself changing the schema all the time. It's as easy to do as adding/changing a class in the project itself.
Generating the ORM layer from the database schema seems OK to me. It does preclude generalizing certain subsets of the schema like e.g. lets say you want to publish a "comments module" reusable across projects that would install its own subset of tables in the DB, as well as provide functions (procedures) to create new tables that link comments to other entities on demand.
The problem with relational databases is that SQL has such poor facilities for abstractions. Whereas the typical language today has higher order functions (some even have higher order classes!) stored procedures are quite limited.
* the fact that the records you can create with SQL are an unbounded combination of every field in every table; in addition, any aggregate or functions applied as well as any renamed fields will further enrich the set of row classes you are able to generate.
* the fact that with (standard) SQL you cannot create anything other than lists of records, whereas objects are directed cyclic graphs
* the fact that objects assume unbounded access to the said directed cyclic graph as if it were in memory, which is a mismatch with optimal SQL querying patterns (the n+1 queries problem)
Databases think in tables and columns and rows and queries and indexes.
If you don't pick an ORM to help manage this translation layer, then you'll end up re-implementing your own. Maybe this is OK, because yours will be simpler for quite some time.
What else are you going to do? Stored procedures? Concatenated strings?
The ideal for me is something that manages connections, lets me write the SQL, and gives me easy access to ResultSets, perhaps in the form of an object.
And yes, stored procedures are extremely useful, especially for minimizing chatter between the server and the database. Use them appropriately. (Although I don't see how they are an alternative to ORMs.)
Only in the loose sense that you have to put the data into an object. That's a tiny portion of what most ORMs actually do.
I just cloned the hibernate-orm repo and, even excluding the .git files and /test/ directories, it has about 30 MB of source. There's a lot in there.
The way code thinks about objects, behaviors and relationships is unlike the way you need to have it stored. Trying to use ORM-generated objects directly in a more complex business logic is, I learned, a recipe for disaster.
And since you're already writing two layers - data translation layer and actual domain layer - then, one may wonder, why not skip the first layer and implement the domain layer in terms of SQL queries?
But there’s got to be some reason people keep embracing it, even if I’m not one of them.
Possible reasons, which you can rephrase as productivity virtues, if you are so inclined:
* I don’t spell well
* I don’t test much
* I don’t care how much time and bandwidth it uses
* I only know Java (or C#, or whatever)
Sounds despicable to me, but could be many small scale Enterprise projects, I guess.
> * I don’t spell well
I don't want to lose minutes or hours debugging a stupid typo.
> * I don’t test much
Writing meaningful tests with my object model without having to care about bizarre SQL semantics.
> * I don’t care how much time and bandwidth it uses
Because ORM will optimize the queries for me, cache results and perform lazy loading when possible.
> * I only know Java (or C#, or whatever)
Using the full power of expressive languages to work with my object models, keep things DRY and reuse code, and have support for different storage engines and sql flavors.
The first pattern, common in frameworks like Django, is to cram the business logic into the ORM instance objects. This creates a tight coupling between the two separate concerns (business logic and data persistence), and will cause problems as soon as the two structures deviate from each other.
The second pattern is to keep the ORM as a purely data storage layer, which is much more flexible but commonly causes duplication of nearly identical structures in separate layers.
For the second, I don't see how skipping the ORM avoids that duplication. Presumably you still need to map your database fields to object attributes.
No one should do that.
The second pattern is also awful. Why would you do that.
I have no problem with SQL, I actually like the language it's very powerful. However mapping query results to objects is soul crushing.
So a few year ago when I became the sole dev in the company, I decided I would try Entity Framework (Microsoft .net ORM)on a new project that needed to be delivered yesterday.
To get even more speed I didn't even design the database as I do usually, I went with the code first approach. It was magical, so much less work to do.
Then something went wrong with Entity Framework it didn't retrieve the right data I think (sorry I don't remember what exactly, but it was a show stopper) and I couldn't find a solution to my problem online.
Then I had to do everything by hand in a rush, I was very late with the project.
I kind of lost interest in ORMs after that but I thought there should be a way to map queries to objects. So I started to develop my own micro ORM with Tuples. I went nowhere fast but while I was searching I discovered that what I wanted to do already existed and was actually named a micro ORM.
Now I use Dapper (Stackoverflow .net micro ORM) and I'm very satisfied with it. Its level of magic is relatively low, so if it doesn't work I can quickly replace it with a manual query without losing much time.
There was literally no choice; I had to work for several companies which were using ORMs. I dealt with the reality of the industry by specializing on the front end.
Those were the dark ages for back end development.
Nowadays front end development has taken the lead when it comes to insanity; React, Babel, GraphQL, Webpack, CoffeeScript and TypeScript... All tools/frameworks which add negative long term value.
I switched back again to back end development a few years ago to escape the front end madness... But now back end development is also starting to degrade; pure functional programming and languages which transpile-to-JavaScript (to run on Node.js) are the latest diseases. Thankfully back end development is more fragmented and there is more room for different tools.
If this continues, I will have to give up on software development and switch to consulting; I will be forced to sell complex (but popular) tools which create problems for businesses and then offer to sell them a solution to solve some of the problems which I created. It's not a joke though, business people really are becoming THAT retarded.
I'd argue that most of the time what you really need is a query builder with the right combinators, and maybe some higher order combinators to wrap those together for even better ergonomics.
For those writing NodeJS one of the best ORMs that I've found is typeorm[0], check it out.
Here are some reasons I like it:
- Typescript-first
- Provides the repository pattern (ex. repository.findOne) & the entity manager pattern (ex. manager.findOne) so you can use whichever you prefer
- Annotation-based entity markup
- Ability to drop down to raw SQL query easily at any time
- Query building ability, special handling for relation queries, it's even got support for building with where params like and and or
- Great documentation
- Ability to programmatically use the introspection that it does for you (accessing the metadata from code is trivial)
- Handles migrations (you can generate them, run them, etc), easy to use programmatically as well
- Supports some low level concepts from my own favorite RDBMS, postgres
it lets you create beautiful sql like declarations
``` payment_types = from(s in Ecto.assoc(order_cycle, :splits)) |> join(:left, [s], p in assoc(s, :payments)) |> select([s, p], %{ type: p.type, card_type: p.card_type }) |> Repo.all() ```
its the first database library that makes queries easy to compose together. On top of that transactions are a snap to put together thanks to Ecto.Multi
What it is missing is a model of _behaviour_.
I've always (ok well, not always, but for the last 15 years probably?) felt the missing piece in what we call 'middleware' isn't to resolve the impedance between object orientation and the relational model, but to somehow come up with a method of representation behaviour/execution for the relational model itself. And do away with all these hierarchies of classes and whatnot. Something conceptually like what we call a method/multimethod in OO, but consistent with the mathematical, set-based lingo of the relational algebra.
But I'm probably not smart enough to figure that out. I hope someone does. I don't think 'stored procedures' is even close to it. Maybe Datalog has it, but I haven't grokked it well enough to say.
A logical thing to do is restrict these kinds of actions to a database module, which exposes a function for each query we want to run against the database. This is a lot like the stored procedure model, with the procedures in the app (and adjustable from the app) instead of in the database. With a structure like this, it isn't so bad to write all queries as SQL files with some template parameters. Maybe there is repeated logic but there is usually a SQL templater for your language that lets you template in table names. There is abstraction, there is clarity about what code is being run, there is control over performance, there is syntax highlighting.
We confirmed what the DBAs said, the latency was only 34 seconds round trip and the database was pretty fast. I installed wireshark to see what was going on. It was querying a table with 80,000 entries, and then for each entry it would do another, "select ... limit 1" query to get the meta data on each record. This loop was abstracted away in an ORM and the third party had very few clients with tables of our size for that particular table.
When it was run on prem it wouldn't take too long, but when it was on cloud it would take 45 minutes. Insignificant amounts of time being spent thousands of times ends up being significant time spent, and this can creep up on you as more things move to be serverless / API driven. A bunch of money and time was spent on figuring out the source of the issue.
If you consider the software development process as a whole, the explicit SQL approach intrinsically guarantees additional scrutiny of the actual SQL statements. If you hide all of this behind an ORM, all that is seen during code review time is some beautiful POCO model with a few extra IEnumerables thrown on it. No one is paying attention to the man behind the curtain and the horrible join that was just created automagically.
Perhaps the answer is to just log and profile all the ORM generated SQL - Sure, but if you could look at the ORM's output (which in some cases is abusively large) and quickly determine if its good or not, why not just write it yourself and be 100% sure from the start?
Developers have been making n+1 mistakes with Raw SQL for years before ORMs became popular.
It doesn't matter if the "DBA or experienced developer" makes a select query that is better than the ORM. If this select query is inside a loop then all bets are off already.
ORMs usually have eager loading options...
I'm kind of curious about what data the select limit 1 queries were returning that the ORM couldn't get in a single query and had to go back per record. Can you shed any light on this?
ORMs have a degree of flexibility. It's possible to write n+1 queries accidentally, especially for developers who are new to the ORM. It's also often possible to address those issues. Sometimes trivially, sometimes not.
The other thing that comes to mind here is that maybe the query was hitting a non-covering index and that was triggering the lookups? In that case a covering index would have fixed the issue?
Curious about this one...
Though... since it sounds like the query was returning the entire table at once eager loading will cause issues of another variety at some point.
Either way, I'd chalk it up to bad design rather than bad tools.
I think people who have used ORMs a long time do not see them as a SQL replacement or as a total database abstraction layer, but as an automation tool for CRUD operations, with some capabilities of doing interesting things with querying, and potentially a methodology for managing database schema and migrations (depending on the tool). At their best, ORMs can tremendously reduce one's requirements for unit tests in particular areas because they have enough structural metadata to typecheck (automatically or compiler time) all the way down to forms in interfaces.
BUT, without a doubt, ORMs are indeed a tremendously leaky abstraction. That said, I would argue that every data store interface is, in that sense, a leaky abstraction. No matter the datastore you're using, be it RDBMS or a specialized NoSQL / search store, you need to learn the hows and whys of how it is structured. Not only should a professional learn SQL, but they should get at least a layperson's knowledge of how the database underneath the SQL works. However, the argument still stands, because when debugging a complex ORM query, you are debugging how it puts SQL together, and then you need to debug what the generated SQL is doing. So you're now multiple layers away from the actual thing you're managing.
Hence the 'final form' of my day-to-day ORM stance: I like to have an ORM around because of the massive automation around entity manipulation, but I think of the ORM in terms of the database and how the database should be structured, rather than as a generic domain model that happens to be mapped to some behind-the-scenes database. Furthermore, I believe it is quite valid to drop down to SQL for the very complicated stuff, especially if you want to trust to the query planning of the database. Once you do, you've broken the total safety of the abstraction, hence my ultimately seeing ORMs as automation tools rather than abstraction interfaces. I have no blame for people who refuse to utilize them, though I'd argue in a design meeting for their use, and hope I wouldn't have to take on all the CRUD queries if I lost the argument, because I'd start writing code to generate them, which then would become a terribly under-engineered faux-ORM :).
"I'm seriously you guys." I was recently working with a Prolog-SQLite binding when it suddenly dawned on me that I should just skip SQL and use the Prolog engine directly as the DB. Modern Prolog systems can handle millions of records, for a lot of applications that's all you need. On the other hand they also have e.g. ODBC bindings.
SWI Prolog can internalize data in flatfiles "Managing external tables for SWI-Prolog"
https://www.swi-prolog.org/pldoc/doc_for?object=section(%27p...
There's also a Prolog graph database coming out in October: TerminusDB.
OOP is enough of complexity to kill most projects—and yet we discuss the dangers of ORM.
“Object-oriented programming offers a sustainable way to write spaghetti code.” -PG
Whether or not parallel arrays of template and value pairs to be anded together is better or worse is debatable.
Some api like prepared statements (supported by the db itself) for building up complex queries step by step would be nice. Does something like this exist?
I feel the same way about Wiki syntax as ORMs.
While SQLAlchemy is great for getting the DDL done, my SQL-fu is such that I wind up just using SQLAlchemy as a glorified connection manager while I construct the SQL strings directly rather than muddy up a sophisticated query by translating it into Python.
The same holds true for Wiki syntax. If I'm already proficient at HTML, it buys me precious little to use this 'easier' syntax if sometimes I'm in Redmine, sometimes I'm in Confluence, or wherever else I land.
Just let me code the page already!
So I spend as much time faffing about with how this particular tool does anchors as I would just doing the "a" tag outright in HTML.
I was with the spirit of the article save for this.
Recently I've been developing some work for one of my client's in the .NET world. There had been ongoing discussions about developers wanting to use Entity Framework as an ORM vs. using stored procedures.
The client already uses SQL projects and DACPACs (effectively a system for specifying the DB in SQL then diffing it between versions to alter the database).
The arguments for and against ORM were largely naive from each side of the fence - the developers wanted strong typing (doesn't require an ORM) while the DBAs wanted to be able to review query execution and suggest changes if there's an issue (you can see the generated SQL for the ORM).
The solution I came up with was to use reflection on the build server as part of the CD process to map the inputs and outputs of the stored procedures into strongly-typed C# methods and objects, generate code for it, and build/pack/push a nuget package back to our feed. It's similar to what EF provides without maintaining the EDMX (which we can do as we don't need to cover every possible data access scenario like EF does). It means that once the database project is checked and the build green-lights, an updated Nuget package that constitutes the DAL is automatically waiting on the internal Nuget feed.
I've found it gives us the best of both worlds. We can do anything we need to in t-SQL and the C# wrapper only cares about the ultimate input/output. I can force the use of parameter sanitization in the wrapper (by not providing any other way to call the procs), and the DBAs can review/amend whatever they want without the developers needing to change their code as the interfaces don't break.
We also don't have to write DAL boilerplate or worry about inexperienced developers getting it wrong and opening injection attack surfaces at the DAL layer.
There's usually a solution to your use case if you look for it is my point.
Most ORMs are heavily inspired by object-oriented programming which fosters the object-relational impedance mismatch. In addition to logic for storing and retrieving data, models often also implement business logic (ActiveRecord is a good example here). This makes for bloated and complex objects that are difficult to work with in the application. A solution can be to lower the level of abstraction and use a more lightweight query builder (like knex.js for Node.js). These kind of tools give you more control in constructing your queries as well as the ability to optimize them.
These tools still require you to understand quite a bit of SQL though and the productivity leap compared to writing manual SQL isn't as high. I believe that query builders are the best compromise we have today for accessing a database from an application.
Regarding the dual schema dangers that are mentioned by the author, I strongly believe that these can be alleviated using code generation tooling that helps to keep your database in sync with your application models (approaches like SQLBoiler in Go where application code is generated based on the database schema are an example here).
The former is frustrating to use in my experience but the latter tends to make SQL easier to work with without taking away any of the power and expressiveness of SQL.
Of course, for complex analysis like in specific periodic data reports, where the filter parameters are mostly known and don’t change constantly with the model, there are diminishing returns to this.
However, when you start writing code that writes other code using string concatenation, big-picture wise I think you are doing it wrong. Look at HTML and how it has developed towards client heavy apps as other example. Encoding is hard, dangerous stuff, and having a library do it for you (like the w3c DOM APIs, or higher abstractions like react) can be invaluable and can make the difference between spaghetti code and gorgeously understandable functional statements.
I would consider part of the profession to be knowing sql. This whole "orms are bad / long live the orm" split attitude is ridiculous. ORMs are fine. Some orms are bad, just like some code is bad, and some frameworks are poorly thought out. I have worked with developers in the past, who I _desparately_ wish were forced to use a good ORM like ActiveRecord, so that they could understand just how far you can get with a solid and good pattern. I've also worked with developers in the past who used an ORM like a 15kg sledgehammer, and had absolutely no idea what was going on.
One thing I noticed early on, is that even using a query builder complicated things a bit as well since it meant that the original SQL string was then broken up into multiple method calls to construct the SQL string. Since the full query wasn't built until the very end, this meant that a helper line needed to be added into the code to get that full SQL query if any issues were found down the road and then taken over into our program of choice (in this case, SQL Developer, since we're dealing with Oracle queries in most cases) and running our additional tests over there.
Our campus ERP is pretty complicated table-wise, so our queries (developed either by myself or by our systems analysts) tend to be fairly complex, requiring multiple joins and other complex logic that I feel would have a very difficult time being translated into an ORM format.
A query builder is still somewhat usable, but for the most part I just stick to mostly straight up SQL queries and make use of prepared statements to help avoid SQL injection and keep life simple so it's easier to move back and forth between the application side and testing the query on the database side :-).
On a side note, I'm not sure if it's mentioned here in the discussion (and it might be less of an issue now than in years past) but I have noted some ORM usage doesn't always choose the most efficient mechanism for things (e.g. pulling results using small, but relatively expensive multiple SQL queries rather than being contextually savvy enough to know that a set-based query would be better suited to retrieve the entire set of results at once).
Overall though, working with SQL and coming up with solutions for things is probably one of the funner aspects of my current position and while using an ORM has sounded like fun in the past, it just hasn't seemed like it's the best fit for our particular workflow/environment.
SELECT a,b,c FROM tab WHERE a=@mysearchkey;
An API should look something like result = sql->command("SELECT a,b,c FROM tab WHERE a=@mysearchkey;",
{"@mysearchkey": val })
and the result should be a key/value form. In some languages, you might get a
typed structure back. No more string escapes. No more forgetting the string escapes.
If you required that the command had to be a constant, SQL injection attacks would be a thing of the past.https://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-bind-para...
String escapes should have been dead a few decades ago -- I don't think any modern platform requires it; they all support parameterized queries natively.
Not sure if much has changed regarding the "reflection techniques" since 2014, but I think Postgres does a fine job reflecting. (Don't know much about others)
For example my favorite library Massive.js (a data mapper) depends completely on reflection. It allows its users to access tables, views, functions, extensions, and even enum types from its Javascript API, without the need for models. This completely solved the data definition redundancy problem for me.
I even made a small layer on top of it to get the constraint information too using the information_schema, and everything is working like a charm.
Plus you get full access to the particular database's features, direct control & understanding of what's being executed when, easier query & performance tuning, no special 2nd pseudo-sql dialect to learn, no big extra stack of leaky abstractions to troubleshoot, etc.
One of the big pitches of ORMs is "you can change your DBMS mid project!" Which sounds cool and has its occasional applications but is something I've never actually needed in over 20 years of development.
Uber did it recently, too. They changed from Postgres to MySQL. But I don't know if ORMs helped them or not.
SQL offers the domain appropriate syntax, while the “host” language allows access to in-scope variables, functions, etc.
Of course there would be some more work in allowing a “mix” of two syntaxes. Another option is a query language as a subset of the language, sort of like C# and Linq.
These “you don’t need an ORM!!!” posts always annoy me because even if you don’t think you’re using an ORM, you probably are. You aren’t mixing raw SQL statements into your business logic, are you? Probably not. You’re probably writing wrapper classes that “map” the “relational” data into the “objects” that your business logic uses, otherwise known as an ORM.
But there’s a big difference between that and an ORM framework that generates SQL. Realistically speaking, you _do_ need an ORM in all cases, but not necessarily an ORM framework.
You also lose compile-time checks.
I'm trying to combine the best of the two approaches in the V language. It has a built-in ORM that uses SQL-like syntax:
uk_customers := db.select from Customer where country == 'uk' && nr_orders > 0
println(uk_customers.len)
for customer in uk_customers {
println('id: $customer.id; name: $customer.name')
}
https://vlang.io/docs#ormRecently I have most experience with EF Core (from .net) which does not have the "select * " issue the article describe (you can select whole entities, but you can also project individual columns if you want.) It uses Linq for expressing queries, which means it is more concise end express relational algebra clearer than SQL.
On the other hand I have tried Hibernate, which had such a verbose and cumbersome query builder syntax that it made you long for plain SQL.
On the write side, the ORM just returns an aggregate, usually based on the PK of the root. Thats's trivial for any ORM.
On the read side, simple queries can be modelled with ORM syntax if you're just trying to fetch a graph of existing objects. Complex queries can be returned with raw SQL that map to custom read models. I tend to wrap both styles in integration tests that ensure the query logic doesn't change, and that the actual mappings don't break.
Both ORMs and SQL have their usages, and they're not mutually exclusive.
This feels like good balance. I want to express my database schema via OO classes. It eases db migrations as the application grows if you use tools like Alembic (same developers as SQLAlchemy).
But use care to avoid SQL injection risk with template queries. SQLAlchemy makes this easy using .bindparams, as does .NET via SqlCommand.
I guess one advantage is you have to learn, it but I really prefer some kind of ORM for more mundane repetitive CRUD. More to get a structured (ha!) interface between the database and the application than for the convenience.
I do wish most of them would stop insisting on putting the cart in front of the horse and make code the primary representation.
It's awful, but not anything an ORM could help with.
Getting that out of the way is exactly what an ORM can help with.
ORMs have their place. Simple CRUD microservice? An ORM can help tremendously. Complex reporting system? Probably not the right tool.
Be careful though. I've run into issues where once you're scaled way up and need the ORM to get out of the way, it can be a beast to detangle if you weren't disciplined.
You wind up having to have a deep understanding of the ORM and SQL, at which point I would argue why bother with the ORM at all? For .NET, I'm much happier with a very very thin layer over the base .NET database client library called Dapper. Unfortunately most shops use Entity Framework by default.
I agree that ORMs are often too complicated, but imho it's still worth it to learn and use one. I'm using ORM loosely though in that I don't think heavy 'Object' and 'Mapping' layers are so important. Mainly you just need a reasonable way to parameterize and compose queries so you can avoid injection and deduplicate logic in a sane way. So I guess my argument is more "use a library" than "use an ORM".
In my experience everyone who says they'll use pure SQL ends up adding gnarly string building logic at some point because the duplication gets out of control. It's better to just find a decent lib that works at whatever level of abstraction you're comfortable with and use that.
IntelliJ has a plug-in.
It’s a nice compromise between crafting strings vs SQL DSL.
As for the objects, you can get very far with everything being a Map until you really need to add a class or two. :)
class Foo {
Long id
String name
...
}
Foo foo = new Foo(db.firstRow("select * from foo limit 1"))
And it all just works if the database columns match the fields of Foo. And if you just do Foo foo = new Foo(db.firstRow("select name from foo limit 1"))
Then you get a `foo` with only the name populated, etc, and you got type safety, easy direct, efficient SQL queries and the ability to test your code without hitting the database, all without imposing any "leaky" abstractions that cause all the problems.Most modern ORM have escape hatch to let you write raw SQL.
Your favorite web dev language will have a dominant ORM. C#, Python, Elixir, Ruby all have a popular ORM that works within a popular framework.
ORM will make working with database and web framework easier.
I do agree with post that querying in ORM can be hard and sometime not possible without raw SQL. Writing query for Ecto, Elixir's most popular ORM, can be tricky when you want to do dynamic query.
Use an appropriate combination of ORMs and raw SQL.
It is a problem if potentially any part of your program can just fail, and that happens if you allow magic ORM-objects to leak onto code paths that aren't written to handle them.
I have nothing against coffee or typescript (and other alternatives) and think they're very useful, but at the end of the day it's really just javascript. But I guess you could make that argument about anything until you get down to machine code.
Most ORMs allow the developer to bypass the ORM and use raw SQL when the need arises, so I don't really see the point of avoiding ORMs.
Across the lifespan of a project, in early stages, a developer would discover that ORMs provide code maintainability and depend heavily on it.
As project requirements increase in complexity, raw SQLs will be required because of performance reasons.
The other issue is how central
If I need something extra, I just write the SQL.
All in all, in general, I program much faster than other people who don't have this setup/flow.
I recreated an API someone was working on for 2-3 hours in 9 minutes. This was the "senior" developer of 55 years old.
Including creating the project, creating the migrations, creating the api and a small test.
It also feels to me like he's talking about a specific ORM - I know ActiveRecord has it's fair share of issues, but from what I know of AR usage and implementation, it either doesn't do what he's (legitimately!) complaining about or does do it in the way he's suggesting.
This is a common problem I've run into in strongly typed languages; can't "just have some data", have to have an object :/
https://www.newtonsoft.com/json vs https://github.com/rangerscience/maptionary
If they had just used basic sql it would be much easier to refactor.
I have worked with SQLAlchemy and Entity Framework, before and like them, but haven’t been able to find that magical demarcation line for when to go raw SQL.
Does anyone have basic rules they use for determining this? Applicable to MVP or enterprise level products
SQLAlchemy's expression language is a good middle ground. I will start there if I need to do more than a couple of joins.
As some people have noted here, I think the biggest problem is that people who only know ORMs will have trouble because their understanding of the database will be limited by their lack of SQL knowledge.
When doing work in python, I don't feel it has a comparable ORM, to where I kind of write my own files that have things like finders and updaters and creators. In most cases, I've found it's much better than SQLAlchemy. Dealing with joins, and things like math, meaning averages, sums, distributions, division, is so much better to be done in the query rather than more basic queries and looping through the results.
There are of course cases like injections to look at, but lots of things I have are calculations of data and showing it, so we're able to handle it with raw queries. Also, ActiveRecord has ways to enforce no injections.
In lots of cases, we've found that using an ORM to start, finding slowness, and moving towards raw queries is a great way to go.
I agree that ORM users should learn SQL, but SQL and an ORM are not mutually exclusive.
Learning SQL with an ORM does remind me a bit of learning memory management with a garbage collector: helps you make better choices as to how you put things together, even if you never directly use the know-how.
In particular I like that it behaves way better using surrogate primary keys (serial id column) instead of natural primary keys... it just answers that question for you that would otherwise result in bike shed conversations.
We minimize logic in the AR models, instead those are in glue objects and this pattern works real well. AR models are mostly there for describing relationships in the code and doing data validations in the code on top of CRUD operations.
1. PostgreSQL or MySQL? And why?
2. Is it possible to build a hybrid database schema? For example, SQLite+JSON?
3. Is it possible to convert XML(XMI) schema to SQLite schema automatically?
4. Is it possible to build a custom file format based on SQLite or hybrid one based on SQLite+JSON?
What it can do is abstract most common operations to do with your dB, and also help with mocking some tests.
I really don’t see the point in these articles. It’s not supposed to save you from learning SQL
I guess there's a certain appeal to only using nails or only using screws, but at some point it doesn't make sense to compromise the quality of your code over ideological purity.
Are ORMs leaky abstraction? Absolutely. Is this the reason to avoid them? Not even slightly. ORMs provide plenty of ergonomic benefits over SQL.
Fun things are coming soon btw, I finally figured out a couple of design problems that have been annoying me for years and the code is close to stable :)
If you don't know SQL, don't interact with an SQL database.
Use a query tool like QueryDSL or Jooq for querying.
It's not surprising that you fail if you don't use the right tool for the job
Where things fall apart is when you want to do things like a subquery on a select field.
Seems more like a limitation of particular implementation than a fundamental problem with the pattern.
Object oriented programing is about clearly expressing business logic. There is no complex business logic in dumping big tables of data.
So, it's not about ORMs, it is object oriented programing that is poor fit for doing reporting.
Relational databases, declarative SQL, functional pure programming are good solutions for reporting.
And you should certainly learn SQL if you want to do any applications with ORM or without. I recommend Joe Celko's SQL for Smarties: Advanced SQL Programming.
Object oriented languages shine when there is a need to create a precise language that facilitates fast and robust communication between domain experts and developers. In situations where you have little data, but a lot of intricate logic. This is where relational databases are simply no good.
Relational algebra and SQL is not a very expressive natural language. SQL limits your vocabulary to 4-5 verbs unless you start writing procedural code in procedures etc. but SQL is not a good procedural language. Relational databases are a solution to specific technical problems of scale, execution speed, atomicity, consistency, isolation, and durability (ACID). They excel at that, not at communicating intention.
You should use ORMs (preferably Data Mapper) if your goal is to solve problem of expressing complex domain specific logic. You use relational databases in that situation because they just work. Data Mapper allows you to isolate your domain model from tricky technical aspects of data storage like indexing and/or not corrupting files during power outage. ORM works very well as long as you will actually be able to ignore technical aspects of speed etc. in your domain model.
You can do the data mapping, querying, migrations, and all this technical cruft manually with a handwritten SQL if you want, but SQL certainly will not address very well the goal of creating an expressive domain model that facilitates robust communication between developers and business experts.
So, given that we addressed the elephant in the room, some other points:
Dual schema dangers: "I much prefer to keep the data definition in the database and read it into the application."
That's perfectly good solution, if you have a lot of data and amount of logic related to data is minimal. If you have little data and a lot of logic you will not be able to store data definition exclusively in database whether you are using ORM or not. Even if you just store SQL queries in your code, then you do store schema structure with SQL, just not explicitly, but implicitly.
Data migrations, rolling release etc. are tricky whatever you do. I use my ORM to dump me SQL that is needed to move structure from point A to point B, since ORM knows the schema it can do that, and then I manually adjust it as needed to massage data etc.
Identities: Not sure if I follow here. It seems like you have issues with auto incremented ids. Auto incremented ids with ORMs are annoying, indeed. My advice is to use UUID generated in the code, then you will have no need to hit database. Also a side note here: if you use ORMs, do not use anything that has any real world meaning for ids. Use UUID, so that you are sure that no domain expert will want to mess with that.
Transactions: The same situation as with speed and migrations. Getting technical details of transactions is hard whether you use ORM or not. You can use stored procedures, but I'm curious what will you do e.g. when you will need external REST API to get in sync or to copy user files to assure full transaction from the user perspective. There is no magic bullet, it's just hard.
Generally, with threadlocal sessions and an application passing orm data class instances around the code freely (which is by far the most common pattern of use), the application will end up doing 10,000x more queries than the programmer would have guessed (this is a literal number and not an exaggeration). Trying to tell the ORM to preload the tree of objects that is going to be accessed is nearly impossible, since the instances go up and down the stack from function to function, each potentially accessing an attribute of an instance loaded as an attribute of an instance many levels back to the original object intentionally pulled from the database.
ORMs make writing the application 90% faster for the first 2 weeks and then 50% slower from then on.
That doesn't mean you are stuck writing straight SQL queries and passing around rows of data, you can sit for a bit and build data access functions that make your life easier and write classes that represent entities from the data, but without your data going through tens of thousands of lines of (extremely well engineered and thoughtful) ORM code that you have no hope of ever understanding well enough that you will avoid catastrophic mistakes that are extremely hard to fix.
And you will make catastrophic mistakes, mistakes you would probably never make with SQL. SqlAlchemy has 5 states that orm objects can be in. If you are using SqlAlchemy and you can't instantly tell me what those 5 states are, and the detailed description of each, you are already making huge mistakes and corrupting data.
To quote one section from the many pages of SQLAlchemy documentation about 'session state management' (and if you don't know all this stuff by heart you will end up learning a lot of it the hard way):
==============================================
The SELECT statement that’s emitted when an object marked with expire() or loaded with refresh() varies based on several factors, including:
The load of expired attributes is triggered from column-mapped attributes only. While any kind of attribute can be marked as expired, including a relationship() - mapped attribute, accessing an expired relationship() attribute will emit a load only for that attribute, using standard relationship-oriented lazy loading. Column-oriented attributes, even if expired, will not load as part of this operation, and instead will load when any column-oriented attribute is accessed.
relationship()- mapped attributes will not load in response to expired column-based attributes being accessed.
Regarding relationships, refresh() is more restrictive than expire() with regards to attributes that aren’t column-mapped. Calling refresh() and passing a list of names that only includes relationship-mapped attributes will actually raise an error. In any case, non-eager-loading relationship() attributes will not be included in any refresh operation.
relationship() attributes configured as “eager loading” via the lazy parameter will load in the case of refresh(), if either no attribute names are specified, or if their names are included in the list of attributes to be refreshed.
Attributes that are configured as deferred() will not normally load, during either the expired-attribute load or during a refresh. An unloaded attribute that’s deferred() instead loads on its own when directly accessed, or if part of a “group” of deferred attributes where an unloaded attribute in that group is accessed.
For expired attributes that are loaded on access, a joined-inheritance table mapping will emit a SELECT that typically only includes those tables for which unloaded attributes are present. The action here is sophisticated enough to load only the parent or child table, for example, if the subset of columns that were originally expired encompass only one or the other of those tables.
When refresh() is used on a joined-inheritance table mapping, the SELECT emitted will resemble that of when Session.query() is used on the target object’s class. This is typically all those tables that are set up as part of the mapping.
=========================================
Yep, sounds like an ORM makes life a lot simpler!
But do learn SQL. Do not use an ORM before you have learnt SQL.
I don't think i'm alone in saying I'd rather hire a developer who is native with an ORM and can dive into SQL when things get thorny, than hire one who's going to fight me on the very merits of an ORM at all.
I mean, hey. To each his own. But lets be clear: this is ridiculous, and if you think it's a position you want to take up, make sure you can get hired holding onto it.
I tolerate an ORM at work. Secretly loathing it. Like much of industrial programming.