ORM 憎恨
2012 年 5 月 8 日
几个月前,我在伦敦的 QCon 大会上,似乎每个演讲都包含了一些关于对象/关系映射 (ORM) 工具的尖刻评论。我想我应该更仔细地阅读发送给演讲者的会议电子邮件,毫无疑问,里面有一些东西告诉我们所有人,每 45 分钟至少要嘲笑 ORM 一次。但正如你所看到的,我想反驳一下这种对 ORM 的憎恨——因为我认为很多都是没有道理的。
对它们的指控可以概括为它们很复杂,并且只提供了一个对关系数据存储的泄漏抽象。它们的复杂性意味着一个艰苦的学习曲线,而且经常使用 ORM 的系统性能很差——通常是由于与底层数据库的交互天真。
这些指控有很多道理,但这些指控忽略了一个至关重要的上下文。对象/关系映射问题是困难的。本质上,你所做的是在两种截然不同的数据表示之间进行同步,一种是在关系数据库中,另一种是在内存中。虽然这通常被称为对象-关系映射,但这里实际上与对象无关。严格来说,它应该被称为内存/关系映射问题,因为它适用于将 RDBMS 映射到任何内存数据结构。内存数据结构比关系模型提供了更大的灵活性,因此为了有效地编程,大多数人希望使用更多样化的内存结构,因此面临着将它们映射回关系以供数据库使用的问题。
映射变得更加复杂,因为你可以在任何一方进行更改,这些更改必须映射到另一方。由于你可以有多个人同时访问和修改数据库,因此问题变得更加复杂。ORM 必须处理这种并发性,因为你不能仅仅依赖事务——在大多数情况下,你不能在内存中处理数据时保持事务打开。
我认为,如果你要像许多人那样对 ORM 大肆批评,你必须说明替代方案。除了 ORM,你还能做什么?我通常听到的廉价攻击忽略了这一点,因为这就是事情变得混乱的地方。基本上,它归结为两种策略,以不同的(更好的)方式解决问题,或者避免问题。这两种方法都有很大的缺陷。
更好的解决方案
听一些批评者的话,你会认为现代软件开发人员最好的做法是自己编写 ORM。这意味着像 Hibernate 和 Active Record 这样的工具已经变成了臃肿软件,所以你应该想出自己的轻量级替代方案。现在我已经花了无数个小时抱怨臃肿软件,但 ORM 真的不符合要求——我带着痛苦的记忆说这句话。在 90 年代的大部分时间里,我看到一个接一个的项目通过编写自己的框架来处理对象/关系映射问题——这总是比人们想象的要困难得多。通常,你会取得足够的早期成功,以至于深深地投入到框架中,只有过了一段时间后,你才意识到自己陷入了泥潭——这就是我非常同情 Ted Neward 的名言,即对象-关系映射是计算机科学的越南[1]。
广泛可用的开源 ORM(如 iBatis、Hibernate 和 Active Record)在很大程度上消除了这个问题[2]。当然,它们不是简单的工具,正如我所说,底层问题很难,但你无需处理编写这些东西的全部体验(恐怖,恐怖)。无论你多么讨厌使用 ORM,相信我——你最好还是使用它。
我经常觉得,对 ORM 的许多沮丧都是关于过高的期望。许多人将关系数据库“视为一个被关在阁楼里,没有人想谈论的疯狂的阿姨”[3]。在这种世界观中,他们只想处理内存数据结构,让 ORM 处理数据库。这种思维方式适用于小型应用程序和小负载,但一旦情况变得艰难,它很快就会崩溃。本质上,ORM 可以处理大约 80-90% 的映射问题,但最后的那一部分总是需要真正了解关系数据库工作原理的人仔细处理。
这就是 ORM 是一个泄漏抽象的批评所在。这是真的,但不一定是要避免它们的原因。映射到关系数据库涉及大量重复的样板代码。一个允许我避免 80% 的这种代码的框架是值得的,即使它只有 80%。问题在于我假装它是 100% 而它不是。Active Record 的创始人 David Heinemeier Hansson 一直认为,如果你正在编写一个由关系数据库支持的应用程序,你应该非常了解关系数据库的工作原理。Active Record 的设计考虑到了这一点,它负责处理无聊的事情,但提供了检修孔,以便你在需要时可以处理 SQL。这是一种更好地思考 ORM 应该扮演的角色的方法。
对 ORM 应该做什么的这种更有限的期望有一个后果。我经常听到人们抱怨,他们被迫妥协自己的对象模型,使其更具关系性,以取悦 ORM。实际上,我认为这是使用关系数据库的必然结果——你必须使你的内存模型更具关系性,或者使你的映射代码复杂化。我认为,为了简化你的对象-关系映射,拥有一个更具关系性的域模型是完全合理的[4]。这并不意味着你应该始终完全遵循关系模型,但这确实意味着你将映射复杂性作为域模型设计的一部分考虑在内。
那么,我是否在说你应该始终使用现有的 ORM 而不是自己做些什么?好吧,我已经学会了永远不要说“始终”。我想到的一个例外是,当你只从数据库中读取数据时。ORM 很复杂,因为它们必须处理双向映射。单向问题更容易处理,特别是如果你的需求不太复杂,并且你对 SQL 感到满意。这是CQRS 的论点之一。
因此,大多数情况下,映射是一个复杂的问题,你最好使用一个公认的复杂工具,而不是在亚洲发动一场陆地战争。但随后是之前提到的第二个选择——你能避免这个问题吗?
避免问题
为了避免映射问题,你有两种选择。要么你在内存中使用关系模型,要么你不在数据库中使用它。
在内存中使用关系模型基本上意味着以关系的方式编程,贯穿你的整个应用程序。在许多方面,这就是 90 年代的 CRUD 工具为你提供的。它们非常适合那些只是将数据推送到屏幕上并返回,或者你的逻辑在 SQL 查询方面得到很好表达的应用程序。有些问题非常适合这种方法,所以如果你能做到这一点,你应该这样做。但它的缺陷是,你经常做不到。
当谈到不在磁盘上使用关系数据库时,出现了许多新的冠军和旧的记忆。在 90 年代,我们中的许多人(是的,包括我)认为对象数据库将通过消除磁盘上的关系来解决这个问题。我们都知道结果如何。但现在有一批新的NoSQL 数据库——它们会让我们绕过 ORM 的泥潭,让我们对数据存储进行震撼和惊叹吗?
正如你可能已经了解到的,我认为 NoSQL 是一项需要认真对待的技术。如果你有一个应用程序问题,它可以很好地映射到 NoSQL 数据模型——例如聚合 或图——那么你可以完全避免映射的麻烦。事实上,这通常是我听到团队选择 NoSQL 解决方案的原因。我认为,这是一条可行的道路——因此我对提高我们对 NoSQL 系统的理解很感兴趣。但即使如此,它也只在应用程序模型和 NoSQL 数据模型之间的匹配良好时才有效。并非所有问题在技术上都适合 NoSQL 数据库。当然,还有许多情况是你无论如何都必须使用关系模型。也许这是一个你无法跨越的公司标准,也许你无法说服你的同事接受一项不成熟技术的风险。在这种情况下,你无法避免映射问题。
因此,ORM 帮助我们处理了大多数企业应用程序的实际问题。确实,它们经常被误用,有时可以避免底层问题。它们不是漂亮的工具,但它们解决的问题也不完全是可爱的。我认为它们应该得到更多的尊重和更多的理解。