Golden Rule Of Sanity: No ORM/ODM
Table Of Contents
Many teams I have interacted with, including the team I work with, decide MongoDB for obvious reason of flexibility. MongoDB is often chosen for its flexibility. A common choice for Node.js projects is Mongoose, a popular Object-Document Mapping (ODM) tool. During my interviews with engineers, I have noticed that many of them only know MongoDB through Mongoose a popular ODM. There are many engineers I have interviewed only know MongoDB from Mongoose. This isn’t an isolated case; numerous SQL teams also rely on Object-Relational Mapping (ORM) tools.
Most of the people, have started with SQL, their first NoSQL database interaction comes way after they have learned and developed couple of applications on SQL Database. They have their schema defined, it was mapped to their object as it is. And most of the time, they do simple CRUD over their schema and if needed some simple joins. That gives them an advantage of speed which will help them perform 50-80% of v1 of the application.1.
When using an ODM, it sits between your code and the database layer, translating your models and methods into queries understood by the database. This additional layer introduces extra processing time, compared to using official drivers or SDKs that allow direct communication with the database in its native dialect. If a task can be accomplished in zero lines of code without an ODM, why add the extra layer? Furthermore, tools like Mongoose add additional code that you have no control over, which inevitably affects query execution time.
Teams choose NoSQL databases for their flexibility, but they often compromise this flexibility by introducing ODMs like Mongoose. Need to add a field to a collection? You must first add it to the schema, which must be defined beforehand. What if you want to make the field optional or change its data type? The decision-making process starts resembling that of a SQL database, and in hindsight, migrating to SQL might seem like the easier and more sensible choice.
In contrast, using official drivers usually requires defining your JSON data, inserting or updating it, and you’re mostly done. At most, you may need to include methods like ObjectId or ISODates for specific data conversions, which is also a chore when you use ODMs.
One of my biggest complaint about Mongoose is they add
default values if the value is not provided during inserts or updates. And this alone kills the very purpose of MongoDB. When you store nulls and default values in your fields, and thus include the fields regardless you need them or not, that alone kills the biggest advantage (and probably reason of existence) of NoSQL, flexibility. And with this approach you have worst of both worlds, rigidity of SQL without the performance RDBMS offers.
To remove the defaults and nulls, you have to write another layer of code in the form of plugins, or middlewares, and that as I said earlier bad for your overall performance. I am still not talking about soft delete plugin which I think was an important point of confusion for the people who don’t bother to look to documentation. Such reach ecosystem of plugins adds (or modify) fields in the query, which won’t be revealed until you debug your queries, and thus you won’t even be aware about some fields that are bottlenecks of the system.
Talking with a team, I have come to know their biggest performance bottleneck, a query takes 1000s of Query Targeting Ratio2, it gives result set of one by scanning entire collection. And the query was not even written by the team, they were using Mongoose to get the count for the pagination. Mongoose flags this method as deprecated now, and they have a replacement, but I have found this method being used in January 2023, definitely sometimes after that said method was deprecated.
Lean Queries and Documents.
The inclusion of nulls and default values in Mongoose can become a real nuisance. I’ve encountered collections where the average document has only five fields, but a few documents have fifteen fields. To accommodate these exceptional documents, which represent less than 10% of the collection, all other documents need to carry ten extra fields with null and default values. Similarly, in code that queries only one field, Mongoose may add three new fields due to plugins. Therefore, using ODMs like Mongoose will inevitably add fields and values to your documents and queries that you didn’t explicitly add and won’t directly use.
With a layer sitting between your code and your database, there are always some doors for vulneribility. Each additional layer we don’t control, can become a security nightmare later. And all this at an additional dependency which has to be installed and initialized, I haven’t seen an ODM who don’t use official driver as their dependency.
I have been in the teams who did their diligence about ORMs and ODMs, I have been with and seen teams who developed robust and scalable applications both with SQL and NoSQL, and in the wild I have seen two successful use of ORM/ODMs, room in Android and Redis OM, and the striking similarity of both of these libraries are that both are provided by the developers of the same platform and database we are supposed to use with. But apart from that, avoid ORM and ODM from a thousand feet and develop calmly.
May the force be with you.
A metric used in MongoDB to measure effectiveness of the query, a query targetting ratio is a ratio between your result set and the documents MongoDB needs to process to fetch the result set. An efficient query has targetting ratio closer to 1, and MongoDB has good way of indexing to achieve that. ↩︎