关于前端:Node-连载-29Nodejs-ORM-在语雀的探索与实践

32次阅读

共计 6521 个字符,预计需要花费 17 分钟才能阅读完成。

本文是 2021 年 12 月 26 日,第三十五届 – 前端早早聊【前端搞 Node.js】专场,来自蚂蚁金服 语雀前端团队 —— 小珲的分享。感激 AI 的倒退,借助 GPT 的能力,最近咱们终于能够十分高效地将各位讲师的精彩分享文本化后,分享给大家。(完整版含演示请看录播视频和 PPT):https://www.zaozao.run/video/c35

残缺 PPT 请分割小助手(vx:zzleva)获取

注释如下

大家好,我是来自语雀的小珲,是一名全栈开发工程师。
在本次分享的内容如下:

  1. 解释什么是 ORM,以及它在 Node.js web 利用中的应用和优缺点。
  2. 大抵介绍目前比拟常见的两种 ORM 模式 – Active Record 和 Data Mapper,并对它们进行简略比照。
  3. 用架构图和伪代码来具体介绍 ORM 的构造,包含其中的重要局部和相干实现。
  4. 应用 ORM 时可能遇到的问题以及相应的优化措施。

什么是 ORM

为了关照纯前端的同学,我将先展现一个简略的 demo 来演示 ORM 的应用。咱们假设有三张表,用户表、文章表和评论表,它们之间的关系能够用图体现进去。每篇文章只能有一个作者,每个文章能够有多条评论,每一条评论只能属于某一篇文章。接下来咱们来看看 ORM 在应用时,如何表白数据库中的关系,并应用它进行业务查问和展现。

首先,咱们会应用 ORM 来形容三个实体,包含用户、文章和评论。咱们将应用 user 类来对应用户实体,应用 comment 类对应评论实体,应用 article 类对应文章实体。在 article 类中,咱们将形容刚刚提到的两个关系,即每篇文章有一个作者,每篇文章有多条评论。咱们将依据本地数据库的设置,连贯到数据库,进行初始化操作。在初始化函数中,咱们会首先连贯到数据库,而后对这三张表进行数据清理。接下来,咱们将演示如何应用 ORM 进行增删改查操作。

咱们将先创立一个用户,并应用 ORM 性能查问出该用户,而后对其进行简略批改,并从新查问后果。接着,咱们将创立一个文章,并增加两个评论。而后,咱们将应用三种不同的形式来查问文章以及与之相干的作者和评论。在执行后果中,咱们能够看到每个操作所对应的 SQL 语句和调用,以及查问到的后果。在上面的三个不同的 API 调用形式中,生成的 SQL 语句都是类似的。最终,咱们将失去一篇文章及其相干的作者、评论以及其余信息。通过这个 demo,咱们能够看到如何在 Node.js 中应用 ORM 进行增删改查操作。

回到主题 什么是 ORM? ORM 是对象关系映射(Object Relation Mapping)的缩写,它将数据中表对应着的开发代码或内存中的 model 类与数据库中的某一张表对应。数据表中的每一条数据对应着 model 类的一个实例,数据表中的某个字段对应着 model 类的一个成员变量。应用 ORM 能够将数据库中的数据映射到开发代码中,从而不便地操作数据库的增删改查。

应用 ORM 有很多长处,例如 ORM 会对查问和更新操作进行数据预处理,从而避免 SQL 注入的危险。另外,ORM 屏蔽了间接编写 SQL 的细节,使得开发人员不用本人写 SQL,这对于 SQL 不纯熟的人来说是一个益处。此外,因为 ORM 以模型为根底,因而反对 MVC 的开发架构,并且能够映射所有数据库表到内存的 model 中,这有助于组织和复用代码,防止了到处写 SQL 的难堪处境。

然而,应用 ORM 也存在一些毛病。例如,因为 ORM 定制以及组合 API 生成的 SQL 的个性,有时主动生成的 SQL 可能不是最优的计划,这可能会导致性能问题。此外,为了解决各种简单的逻辑,model 也会变得很简单,解决查问后果可能会有不必要的对象深拷贝,这会影响利用的性能。同时,ORM 为了适配 SQL 满足的各种业务场景,有很多 API 须要学习,这也是一种老本。另外,对于一些奇怪的查问需要,ORM 可能无奈满足,此时只能手写 SQL。这些是我总结 ORM 的一些长处和毛病。

Active Record & Data Mapper

接下来介绍两种常见的 ORM 模式:Active Record 和 Data Mapper。Active Record 翻译成中文就是被动记录模式,是一种架构模式。之前展现的 ORM 是 Active Record 模式的。

Active Record 的简略总结是一个对象,同时蕴含了数据库对应的属性字段和相应的业务的增删改查操作,也就是说 model 打包了这一个域该有的所有性能。比方,用户有了 user model 就能够间接应用它,并对它以及 user model 实例进行一些业务的编码。这种类型的 ORM 简直都有一个特点,就是所有 CRUD 操作都打包在一个 model 中。业务中只须要依据本人的我的项目和数据库设计去派生出对应的 base model 的子类。user model 继承了 base model 所有的 API,同时也会蕴含本人特有的业务 API,比方查问某个性别的用户、某个年龄段用户等等。

Active Record 类型的 ORM 应用上更加合乎咱们的直觉,应用起来更不便。数据库有多少张表,就对应多少个 model,每个 model 有哪些操作,都在这个派生进去的 model 中实现。它代表的是咱们的数据结构与模型对象高度耦合,因而可能更适宜一些业务逻辑比较简单的中小型利用。

咱们之前曾经展现了一个属于 Active Record 类型的 ORM demo,因而在这里就不再多作解释。接下来,咱们将介绍另一种 ORM 类型,即 Data Mapper 类型。咱们将通过一个 demo 来阐明这种类型的 ORM,其中波及到的模型包含 user、article 和 comment。回顾一下它们之间的关系:每篇文章有一个作者,每个用户能够有多篇文章,每篇文章有多条评论,每条评论只能归属于一篇文章。尽管 Data Mapper 类型的 ORM 在 JavaScript 中并不是很风行,但咱们将应用一个名为 TypeORM 的罕用 ORM 来进行演示。

首先,咱们须要申明实体,别离是用户(user)、文章(article)和评论(comment)。在每个实体中,咱们申明可能用到的属性和实体之间的关系。例如,用户可能会有多篇文章,而一篇文章只能有一个作者和多个评论,每个评论只能属于一篇文章。这与之前讲过的 Active Record ORM 相似,但有一个不同点是这些模型不再蕴含根底的数据操作(例如增删改查),而只用于展现数据,例如名字的展现可能须要加上大写等非凡的展现。

Data Mapper 的实现次要是为了适配某个实体或几个实体的一些根底业务操作。咱们以文章(article)为例,实现一个 Data Mapper,外面会有一个 API,用于依据以后文章的 ID 获取其作者(article)和评论(comment)。在 API 中,咱们应用 Data Mapper 提供的根底 API 去做一个简略的查问。因为数据库中曾经有了数据,咱们间接去查问,而后生成一个 circle 并查找到想要的文章,其中蕴含作者和两个评论。

Data Mapper 模式与 Active Record 模式的不同点在于,它将数据存储层与畛域层解耦,模型不再承当增删改操作的性能。Data Mapper 能够同时解决一个或多个实体类的利用,例如连表查问和对立的数据插入操作等业务操作。如果某个业务须要对数据一致性有较强的要求,并波及多个实体,Data Mapper 能够间接在其中进行操作。

与之前的 Active Record ORM 不同,如果波及多个模型,咱们可能须要独自应用一个服务(Service)将这些模型联合起来进行解决。因而,Data Mapper ORM 更适宜解决多实体类的利用。

ORM 的形成

咱们接下来将解说 ORM 的形成,其中咱们将重点解说 Active Record ORM,这是咱们罕用的一种类型。这些例子都是伪代码。

我将 Active Record ORM 的构造分为两层,第一层为数据抽象层,蕴含罕用的 Base Model,通过继承 Base Model 来创立业务 model。

  • API 都通过 Base Model 进行调用,其余根底性能依附于 Base Model。
  • Hooks 是插入到 API 执行过程中的钩子函数,能够对特定 model 的字段在执行某个操作时进行通用解决。
  • Validations 是 ORM 进行数据预处理的必要局部,应用它能够进步利用的安全性和升高数据库执行 SQL 时的数据类型转换压力。
  • Transaction 是对数据事务的形象和实现,对于一些数据一致性要求高的业务很有必要。
  • Relationships 是关系型数据库的外围,每个 model 与 model 之间的关系对应数据库的 ERD。
  • Migrations 是 ORM 的一个工具类型的性能,用于同步数据表构造以及数据勘误。
  • Dirty Check 是检查数据是否更新的性能,在 Hooks 中应用较多。
  • Data Transformation 是将查问后果转换为 model 实例,或将查问条件转换为数据库可能辨认的数据类型的性能。

第二层为数据拜访层。

  • Dialect Adopter 是外围性能,将 model 的 API 调用转换成对应的 SQL,并在转换过程中抹平 ORM 会适配的不同数据库之间的方言差别。
  • Connection Manager 用于治理 ORM 在利用中的数据库连贯。
  • DB Driver 是数据工具,用于与数据库进行交互。

日志模块是开发和运维中必须的,贯通整个架构。

数据抽象层

在 Base Model 中,数据表中的某个字段对应着 model 类的成员变量,这是对象关系映射中的重要关系映射。

DataType(数据类型)次要用于体现数据类型,作为 JS 根底类型与数据库数据类型的桥梁,记录了数据库类型和对应的 JS 数据类型,并能在两种语言之间转换数据类型。它还须要表白该类型的 SQL,例如,如果数据类型是 integer,在 MySQL 中体现是什么样子的,在 post Grace 或 circulate 中体现又是什么样子的类型。这样,最小的 ORM 最小单位类就成型了。

Attribute(属性)用于表白数据表中的数据字段,它可能与数据表中的字段设置大抵统一,包含是否容许为空、是否有默认值、数据类型等。在 ORM 中,Attribute 还有一个重要的性能,就是数据验证,能够设置一些预置的规定或用户自定义的规定来验证数据的合法性。

在 Active Record ORM 中,Base Model 是其外围组成部分之一。它蕴含了 CRUD 在内的所有根底 API,同时还要可能读取用户派生的 model。在 Base Model 中,还有针对数据库与利用开发语言之间的不同命名习惯的 name 和 column 成员变量。初始化 model 时,还会有用户设置的 Hooks 和 Validation,用户能够自定义 set/get 办法来对某个字段做一些自定义的操作,在设置字段值的时候主动执行相应操作,比方用 text 类型数据库的字段来存储 JSON 字符串。此外,base model 还会记录各个 model 之间的关系,如 has_one、has_many 等关系。

Hooks 是指一些在函数执行前或执行后须要执行的操作,Hooks 的应用则能够升高业务代码的复杂度,缩小工作量,它须要留神一下几点。

  • Hooks 须要可能叠加某个操作,以便解决多种逻辑,这些逻辑不会相互影响。
  • Hooks 须要有一个原函数,它的入参和返回值类型不能被批改。Hooks 的实现形式有很多种,其中比拟直观的一种形式是面向切面编程。在 ORM 的 API 中,一些 API 能够配置或须要配置 hooks,例如 create、update、destroy 等。
  • Hooks 的实现须要留神不同的 API 入参和返回值可能不同。
  • 实现 Hooks 时须要留神动态成员办法和成员办法、函数执行上下文等问题。
  • 在应用 AOP 实现 Hooks 时,咱们还须要思考如何跳过 Hooks 的执行。

接着咱们来看一下 Transaction 的简略实现。在 SQL 语法中实现事务并不简单,个别应用 begin 开始事务,执行业务 SQL,而后应用 commit 提交或 rollback 回滚事务。在代码中实现事务,咱们能够提供一些根底的 API,如 begin、commit 和 rollback。在事务开始时,可能须要设置事务的隔离级别。通过这些 API,咱们能够放弃业务代码的数据一致性。然而,在实现事务时,咱们须要思考如何主动 commit 和 rollback,而不须要手动调用 commit 和 rollback。

数据拜访层

在数据抽象层中,为了适配罕用的数据库类型,须要一个两头态去表白 ORM API,这个两头态被称为 Spell。它能够表白理论的 SQL 命令,依据模型的类型去判断操作的数据表,表白查问所需的字段以及罕用的 SQL 关键词表达式。

Dialect 须要解析 Spell 表达式,依据方言类型生成特定的 SQL。例如,ORM 须要官网提供反对的 MySQL、Postgres、SQL Server 和 SQLite 的 Dialect。因而,须要为每种数据库类型编写一个 dialect。咱们能够将 Spell 表达式的解析形象成规范的接口,这样开发者就能够实现本人的方言,甚至不仅限于 SQL,还能够是其余类型的查询语言。这样咱们就能够应用 ORM 的 API 进行各种类型的查问。

整个查问的执行过程是这样的:假如咱们应用 user model 去查问某个用户数据,咱们在 user model 中应用 find 办法并依据传参生成对应的 Spell 对象。而后咱们在 Connection 类中治理数据库连接池和方言(即 Dialect),实现一个 query 办法,在其中调用 Dialect 生成 SQL,应用 SQL Driver 执行 SQL,最初通过进行数据转换并返回后果。

模块之间的关系

让咱们再深刻理解各个模块之间的关系。首先,数据层面上最次要的实体是 Model。在执行查问操作时,Spell 作为一个两头态,它连贯了数据抽象层和数据拜访层。同时,Dialect 负责适配数据库方言和生成特定的 SQL。这个构造还能够扩大出更多的性能,不仅限于 SQL 查问。

else

咱们思考一下一个绝对成熟的 ORM 还须要哪些改良?

ORM 问题 / 优化

缓存查问

通过适当的操作来升高数据库的 QPS,例如在多个 service 办法中反复调用某个 Model 的查问时能够应用缓存技术,屡次调用时只应用一次后果。

为了防止屡次查问,能够通过缓存来保留查问后果,屡次调用同一个查问时就能够间接应用缓存后果,从而升高数据库的 QPS。应用这种办法的时候,还须要思考到缓存的刷新问题,例如,在更新数据时,能够在 update 中增加一个 Hooks,更新时主动刷新缓存。

正当应用 Hooks

应用 Hooks 是在应用 ORM 时常见的问题。Hooks 能够大大简化代码,例如咱们能够依据文档内容是否更改来更新其更新工夫等字段。在某些简单状况下,咱们可能须要在一个 Model 中的 Hooks 中调用另一个 Model 的增删改操作,例如创立文档后可能须要更新 Book 的操作并触发相应字段的更新,这时咱们须要思考它们之间的关联关系和更新程序。

当咱们的业务越来越简单时,例如在更新 Repo 的时候可能须要触发一些 service 办法,而这些 service 办法可能会在 Hooks 办法中间接调用,导致调用链越来越长,越来越简单。这种不标准的写法会使得代码的复杂度十分难以管制,甚至可能呈现循环调用等问题,给老手带来极大的困扰。因而,在应用 Hooks 时须要十分审慎,避免出现相似的问题。应用 Hooks 须要审慎,尽管在平时应用时会感觉很不便,然而当须要重构或进行技术重构时,就可能会遇到困难,甚至引起劫难级事变。

查问优化

此外,在应用 ORM 工具时,须要进行查问优化,因为 ORM 只能依据输出参数做一些简略的优化解决,而对于一些极限状况,须要开发人员本人去留神。例如,在进行 SQL 查问用 in 时,因为条件长度较长,可能会因为数据库引擎的起因导致 SQL 无奈执行或执行效率较低,此时须要将查问条件进行分组,利用 Node.js 进行分批查问,并在内存中组装后果。

在 ORM 的应用中,须要留神不正确应用 ORM 的 API 调用可能会导致生成子查问,从而升高查问性能。

最初

最初,举荐大家浏览 ORM 的源码(https://leoric.js.org/)和《企业应用架构模式》(https://marti…),尤其是其中介绍的两种架构模式。

正文完
 0