反对ORM仇恨

2021-02-09 21:34:37

每当有人问他们应该使用哪个Node.js ORM时,第一个答案始终是“不使用ORM,只需编写SQL”的某种版本。然后通常会以“您需要使用ORM来隐藏所有讨厌的SQL”的方式以相反的观点来攻击该答案。人们似乎总是无视第三个选择:使用包含SQL的ORM!

我完全理解仇恨的来源。在许多ORM中确实存在问题,Thomas Hunter II在他的帖子中很好地总结了为什么您应该避免使用ORM。托马斯列出的要点是

您学习的是ORM,而不是SQL,并且该知识通常不能转移到其他工具。

复杂的ORM调用效率低下。 ORM拥有自己的面向对象的查询语言,它们会尝试将其转换为SQL,这通常非常困难。

ORM不能做所有事情。大多数ORM的面向对象方法无法很好地映射到SQL。对于许多SQL操作,没有等效的面向对象。

这些问题源于以下事实:大多数ORM都旨在将SQL抽象化,以支持某些面向对象的接口。但是并不是所有的ORM都是这样!

正是出于Thomas列出的原因,我创建了一个名为objection.js的ORM。 objection.js的设计目标是允许您尽可能使用SQL,并且在无法使用SQL轻松完成某些操作时仅提供DSL(特定域语言)或自定义概念。我将尽快返回objection.js。首先,我将向您介绍避免ORM的最常见论点:

是的,如果您一次只需要一个表中的一个平面项目清单,那么绝对不要使用ORM!当然,您可以使用联接,子查询和多个查询来访问其他表中的相关项,但这很容易变得乏味。例如,这是一个非常基本的查询,将多对多关系(一个人的孩子)和多对多关系(一个人的宠物)联系在一起

那已经是很多SQL了。如果您想加入更多的关系该怎么办?嵌套关系如何?您还需要在每次要获取相关对象时重复该操作。更不用说该查询的结果是项目的完整列表,而不是对象的嵌套树。

讨厌ORM的下一件事是使用一个库,该库将平面结果列表转换为嵌套良好的对象树,然后继续编写一堆帮助程序函数以将这些联接添加到查询中,从而您无需重复你自己您很快就会意识到,大多数这些帮助器函数看起来都是一样的,只是表名和外键是不同的。然后,显而易见的选择是继续在单个位置将关系定义为对象,并创建一个接受关系描述对象并输出所需联接的泛型函数。

我认为,好的ORM就是:一套使使用SQL更加容易的工具!它使您可以充分利用SQL和基础数据库引擎的所有功能,并且仅针对普通SQL难以实现的事情提供帮助程序和自定义解决方案。

使用objection.js,您始终可以使用查询生成器。您可以构建任何查询并使用任意数量的SQL,但如果需要,它也可以帮助您处理诸如关系之类的重复性内容。给leftJoinRelation方法指定了要连接的关系的名称,而objection.js使用关系映射来创建连接。

现在您可能会想,“这并不太短,我仍然需要在某处定义关系”。是的,但是您只需要这样做一次。如果您的项目是如此简单,以至于定义关系和模型需要花费大量的开发时间,那么是否使用ORM都无关紧要。

上面的joinRelation查询等效于上一个SQL查询的100%,并且仍然会生成结果行的平面列表。您可以使用joinEager方法轻松获得一棵对象树:

在下一个代码块中,是执行该查询所需的最少SQL。需要具有别名的选择才能从平面列表构建对象树。

对于许多ORM,很难或不可能编写SQL概念(如子查询)。由于所有objection.js操作均返回查询生成器,因此编写子查询与使用普通SQL一样容易。例如,获取所有养育至少一只名为“蓬松”宠物的人

但是,如果您已经为Person模型定义了pets关系,那么还有一种更简单的方法可以在objection.js中编写:

但这需要您学习ORM(这是反对ORM的论点之一)。您仍然可以通过编写熟悉的SQL解决方案开始。了解了objection.js的更多信息后,您会发现类似这样的帮助程序,它们可以提高您的生产率。

如果您的应用程序很简单,并且您几乎不需要处理嵌套的数据和关系,那么使用纯SQL或类似knex的查询构建器可能是一个不错的选择。但是,在大多数应用程序中,您希望使用嵌套数据和许多关系,而ORM可能是有用的工具。使用objection.js,您无需妥协。您可以在同一程序包中获得查询构建器的灵活性以及ORM的关系功能。