Is using ORM a bad idea?
ORMs offer a frictionless development experience when working with relational databases. Still, some minds in the industry feel that using an ORM is not a good idea. Of all the reasons presented against using ORMs, the two most commonly given are as follows:
- ORMs abstract away databases, and hence, the important database concepts that are essential for any developer to master usually get ignored
- The use of ORMs results in a performance degradation of the application
It will be interesting to drill deeper into these reasons. It is true that ORMs abstract away databases and handle almost all database interactions for you in a transparent way. You do not need to bother yourself with a deeper understanding of how an ORM is fetching data from the database. If you master APIs offered by the ORM, you will do just good to interact with a moderately complex database through the most commonly used queries. Further, since this almost always works, you never need to figure out what the ORM is doing under the hood for you. So, you indeed miss out on learning how to work with databases. If you are using your ORM to generate a database schema, then chances are that you are missing out on important database building blocks such as keys, associations, relations, and indexes. So, there is an element of truth in the first reasoning. If you do fall into this category of developers, then this lack of database knowledge will hit you the hardest when least expected.
Talking about the performance degradation caused by ORMs, I can only say that this has an element of truth but it is very subjective. I say subjective because the word "performance" cannot stand on its own. When we talk about performance, we usually refer to a benchmark. This benchmark can be the expected performance of a system or the performance of a similar system used for the purpose of comparison. The performance argument against ORMs is usually in the context of a comparison with other ways of interacting with databases, which include, but are not limited to, the use of plain SQL with ADO.NET or the use of micro-ORMs. While micro-ORMs do offer the best performance, the situation with ADO.NET is slightly different. ADO.NET offers barebones, and you need to write most of the code around reading data from datasets/data readers and hydrating objects. Depending on your experience with building performant systems, you may be able to squeeze more performance out of ADO.NET than is offered by ORMs. However, the key phrase is "experience with building a performant system." If you do not have such experience, then it may be better to stick to an ORM as you will tend to not make the common mistakes made while working with databases.
Why ORM is a better bet?
So, why should we use an ORM then? Let's take a step back and think about our options when we do not use any ORM. In the absence of an ORM, you can either use a micro-ORM or put together your own data access layer implemented using something such as ADO.NET. Micro-ORMs take a minimalist approach towards object relational mapping. Some of them use advanced language features and conventions to automatically map classes to a database schema, while others explicitly make use of custom SQL.
Micro-ORM can be called the little brother of ORMs and is an entirely different topic, so I am not going to consider this option for the sake of this discussion. So, we are left with building our own data access layer. Now what does this mean? First and foremost, this means that you are going to spend a significant amount of time in building something that lets you interact with relational data stores. Further, you are going to do this along with numerous other fellow developers from the "No-ORM" camp. Can you imagine how many times this wheel is going to be reinvented? If you are working in an environment where everyone values "code reuse," then chances are that you would try to reuse this data access layer in more than one project, thus limiting the duplication. However, in reality, this rarely happens for two reasons. One, most of the time, we are so busy delivering our own stuff that we tend to ignore work by other teams, work that you can potentially use as is in your project. The second reason is the realization of the fact that the first team that built the original data access layer did it to tailor to their needs and it does not quite fit your own needs. So, you decide to roll your own. So, all of this leads to countless developers building the similar thing all over again for every new project that they start working on.
Next is the challenge of maintaining the quality of such a data access layer. For simple applications, you can write a complete data access layer in a single class, but such an approach usually does not work for complex applications. When requirements from a data store become complex, it becomes challenging to implement a data access layer that can cope with all the requirements. Without proper experience and knowledge of different experience patterns, you are bound to build a data access layer whose quality cannot be guaranteed.
Every data access layer follows a particular pattern to interact with the database. While some data access patterns have stood the test of time, there are others that should be avoided at any cost. Rolling out a home-grown data access layer means having knowledge of what has worked for others and what has not. It also means having a gut feeling about what will work for your project depending on how the project turns out in the future. These things are not easy to come by and people who are usually good at these things have years of experience in dealing with such problems. It is not possible to have such people onboard every time you are building a data access layer. So, chances are that things may go wrong and you would invest time in fixing things. ORMs, most notably NHibernate, have been around for years. The data access patterns implemented in NHibernate are proven to be the best. At the same time, a care has been taken to avoid designs that are considered harmful for database interactions. Being an open source project, NHibernate welcomes contributions and defect reports from the general public. So, NHibernate has become richer and more stable over time. Contributions from the general public mean that NHibernate is built by learning from the mistakes of hundreds of developers and by listening to the needs of a large community of developers around the world. Not using NHibernate or any mature, open source ORM for that matter would only mean shunting out such a huge source of common knowledge from your system and reinventing the wheel.
Non-functional features required by the data access layer
Besides the above, there are some non-functional requirements that every data access layer would need to offer sooner or later. The following is a representative list of such features:
- Logging: Logging probably is one of the most important non-functional requirements. You would need to log not only the errors and exceptions but also important information such as details of the SQL query sent to the database and the data returned from the database.
- Caching: Caching may sound a simple thing to implement but as your domain grows complex so do the data querying patterns. Such complex data query patterns have very different caching requirements, which are not trivial to implement.
- Connection management: You might think that ADO.NET offers connection management and connection pooling. Indeed, it does. But there are more features around connection management that we need. In real-life applications, you need the ability to reuse connections across multiple queries, be able to make new connections, disconnect the existing connections at the right time, and so on.
- Transaction management: As with connection management, ADO.NET does offer basic transaction management but the needs of most of the real-life software around this area can soon grow complex.
- Concurrency management: Concurrent updates to the same data from multiple users may result in defects that are difficult to find out. Being able to know whether you are working on stale data or not is critical to building robust applications.
- Lazy loading: Lazy loading is when you load only the part of an object that is needed at the moment. We will cover lazy loading in greater detail in the coming chapters. For now, it is worthwhile to note that lazy loading is not a simple feature to implement.
- Security: Your data access layer is the primary gateway to the critical data that you store in your database. The security of this data is paramount, and hence, any data access layer needs to be secured and should be hardened against attacks such as SQL injection attacks.
- Batching of queries: Some queries can be batched with other queries and executed at a later time. This improves the throughput of the SQL connection and hence improves the performance. Not all data access layers are intelligent to batch and execute queries at a later time.
It is unlikely that your home-grown data access layer will have all of these features. You might manage to build some of these features, but getting all of the features in would practically not be possible. This can mainly be driven by the fact that your main objective on the project is to deliver business functionality and not build a fully featured data access layer. Even if you do manage to put together a decent data access layer that has most of the above features, what are the odds of you having tested every edge case and fixed most defects?
On the other hand, any mature ORM would offer most of these features. Since this book is about NHibernate, I will concentrate on NHibernate here. NHibernate has all of these features built into it from the beginning. These features have not only been timely tested but have also taken shape according to the real needs of the developers. Because it is an open source project, a lot of community feedback and contribution has gone into shaping these features. So, you are guaranteed to see superior implementations of most of these features.
Given all of the preceding advantages, would I consider using an ORM a bad idea? I think not. On the contrary, I would consider using an ORM to be a good idea.