Why I gave up on ORMs

My day job is working on backends and that usually includes lots of SQL. Many people are familiar with the idea of an ORM library that maps runtime objects to database objects. (An ORM library is much more than that, of course, but that’s not what this is going to be about)

Before coming to go, I made heavy use of ORMs in C# (namely Entity Framework). As one does, I looked for an ORM in go and found GORM, probably the most popular go ORM. I found it to be straightforward and easy to use at the beginning, so good that I used it in two projects without thinking much about it. I liked the fact that I never had to write any SQL myself - mostly because I was barely able to write a valid statement withour googling first.

Over time, I’ve come to the conclusion that GORM is not a good library for me. First of all, there is a lot of magic happening in the background. GORM makes heavy use of runtime reflection and it is not easy to debug.

The docs aren’t great and don’t go into much detail. There are a lot of struct tags that sometimes don’t seem to do anything. When I opened an issue three weeks ago, I didn’t get any response. A short look into the issue list revealed that I’m not alone with this. I was displeased and started to look elsewhere, mostly because of one reason: I noticed that I didn’t understand the library. Usually go code is extremely easy to read and understand which I value immensely.

Dependencies that one doesn’t understand are dangerous, especially if they don’t seem to be supported anymore. The undocumented behaviour and my unwillingness to code around that lead me to try something new. After all, I’m in this to learn.

I went with two packages to replace my database layer, migrate and sqlx. Both require you to write most of your SQL by hand, something I always avoided. In the three weeks I’ve been using this library I’ve improved my skills considerably since both migrations and general queries are mostly hand-written.

But, apart from learning better SQL, I am especially happy about one thing: the magic disappeared. I know what happens when I query a record with associations because I can decide for each query whether I want to load the associations or not. I can decide if and how I store or update related records. I remember I once tried to update a record with a nonexisting ID, the record got saved instead of updated. I’m still glad my tests caught that error before it went into production, but I’m especially happy that this won’t happen again.

I don’t think I’m going back anytime soon…