什么是 ORM?
ORM提供了一种更简单的方式来与应用程序中的数据库交互,它允许开发人员使用对象来处理数据。
TypeScript 的完美 ORM 是什么?
让我们看看一个 TypeScript 和 NodeJS 的完美 ORM 应该具备哪些理想特性,以及为什么这些特性非常重要,以及现有 ORM 在某些方面的不足。
完美 TypeScript ORM 的前五个特性是:
- 可序列化的查询:
在系统的不同层之间传输查询的能力对于灵活性和简洁性非常有用。例如,前端/客户端 可以使用相同的(ORM)语法将查询传输到 后端/服务器,避免了在流程 GraphQL => ORM => SQL 中出现类似 GraphQL 的额外语法的需要。流程可以简化为 ORM => SQL,具有以下优点:
- 避免需要伪语言来定义查询(上下文切换)。而是使用标准的 JSON 作为查询的语法,使其完全声明式和可序列化。
- 避免应用程序构建过程中需要额外的服务器和步骤。
- 编辑器/IDE 对查询的本机支持,无需任何自定义插件/扩展。
- 本机 TypeScript:
使用 JSON、类和装饰器来发挥 TypeScript 的威力。
- 由编写应用程序的相同语言本地验证的类型安全查询和模型。
- 上下文感知查询允许根据查询的不同部分自动完成适当的运算符和字段。
- 使用标准类和装饰器定义实体,避免了需要专有 DSL、构建过程中的额外步骤和编辑器的自定义扩展。
- 多级运算符:
诸如 filter、sort、limit、project 等操作可以在查询的任何级别(包括关系及其字段)上工作。
- 跨数据库的一致 API:
以一致的方式编写任何数据库的查询,然后透明地优化这些查询以适应配置的数据库方言。
- 通用语法(语言无关):
在任何语言中编写查询的能力为许多可能性打开了大门,甚至可以将 ORM 移植到其他语言中。例如,JSON 是几乎所有现代语言中的一级公民格式,包括Python、Rust和其他语言(除了 JavaScript)。
为什么当前的前三个 TypeScript ORM 在这些功能上表现不佳?
TypeORM 缺少什么以成为完美的 ORM?
- 缺少 100% 可序列化的查询:
请注意,LessThan
运算符是一个必须导入和调用的函数,用于创建一个用于否定条件的查询,这导致无法序列化查询。
import { LessThan } from 'typeorm';
const loadedPosts = await dataSource.getRepository(Post).findBy({
likes: LessThan(10)
});
- 缺少本机 TypeScript:
查询转化为字符串,因为 relations
和 where
可以接受任何字符串,因此任何无效字符串都可以放在那里。
const posts = await connection.manager.find(Post, {
select: ['id'],
relations: ['< anything can go here >']
});
- 缺少跨数据库的一致 API:
在 TypeORM 用于 MongoDB 的部分中,有一个不言自明的警告。
- 缺少通用语法(语言无关):
它依赖于闭包来支持高级查询(并非所有语言都支持闭包)。
Prisma 缺少什么以成为完美的 ORM?
- 缺少本机 TypeScript:
- 在自定义 DSL 和 TypeScript 之间进行上下文切换会使此过程变得难以维护。
- 需要在构建过程中执行额外的步骤,以从自定义 DSL 生成相应的文件。
- 必须安装自定义扩展程序才能从编辑器中获取(基本)自动完成 DSL,这远不及 TypeScript 那样好用和可靠。
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
email String @unique
name String?
role Role @default(USER)
posts Post[]
}
- 缺少跨数据库的一致 API:
Prisma 暴露 MongoDB 的低级细节,这些细节可以由 ORM 封装起来。
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
// Other fields
}
- 缺少通用语法(语言无关):
它依赖于自己的专有 DSL 来定义模型。
Mikro-ORM 缺少什么以成为完美的 ORM?
- 缺少 100% 可序列化的查询:
请注意,该查询使用两个单独的方法,一个用于 update
,另一个用于 where
,这会阻止其查询的序列化。
const qb = orm.em.createQueryBuilder(Author);
qb.update({ name: 'test 123', type: PublisherType.GLOBAL })
.where({ id: 123, type: PublisherType.LOCAL });
- 缺少本机 TypeScript:
解析为字符串,任何字符串都可以放在 fields
数组中,因此查询失去了类型安全性的可能性。
const author = await em.findOne(Author,
{},
{
fields: ['name', 'books.title', 'books.author', 'books.price']
}
);
- 缺少多级运算符:
如果需要过滤关系的记录或对其进行排序怎么办?根据在 Mikro-ORM 文档中所看到的内容,这似乎无法以类型安全的方式实现。
- 缺少跨数据库的一致 API:
import { EntityManager } from '@mikro-orm/mongodb';
const em = orm.em as EntityManager;
const qb = em.aggregate(...);
Mikro-ORM 暴露 MongoDB 的低级细节,这些细节可以由 ORM 封装起来。## 上述原因促使我编写了一种新型的ORM,我称之为nukak。
那么,为什么nukak是最接近_完美_ _ORM_的选择? 简而言之,因为它是根据以下原则设计的,请看下面的介绍。
- 100%可序列化的查询:
即使是_insert_和_update_操作也有完全可序列化的API。
const lastUsers = await querier.findMany(User,
{
$sort: { createdAt: -1 },
$limit: 20
},
['id', 'name', 'email']
);
- 具有真正类型安全的TypeScript原生查询:
每个运算符和字段都根据上下文进行验证。例如,$project运算符的可能值将自动取决于级别。
const lastUsersWithProfiles = await querier.findMany(User,
{
$sort: { createdAt: -1 },
$limit: 20
},
{
id: true,
name: true,
profile: {
$project: ['id', 'picture'],
$required: true
}
}
);
- 多级运算符:
运算符适用于任何级别。例如,$sort运算符可以以类型安全和上下文感知的方式应用于关系及其字段。
const items = await querier.findMany(Item,
{
$filter: {
salePrice: { $gte: 1000 }, name: { $istartsWith: 'A' }
},
$sort: {
tax: { name: 1 }, measureUnit: { name: 1 }, createdAt: -1
},
$limit: 100,
},
{
id: true,
name: true,
measureUnit: {
$project: ['id', 'name'],
$filter: { name: { $ne: 'unidad' } },
$required: true
},
tax: ['id', 'name'],
}
);
- 跨数据库的一致API:
它的_API_在不同的数据库之间是统一的,它在幕后完成了神奇的工作,因此相同的实体和查询可以透明地在任何支持的数据库上工作。例如,这使得从文档数据库切换到关系数据库(或反之亦然)更加容易。
- 跨语言的通用语法:
它的语法是100%标准的_JSON_,为其他语言提供了许多可能性,如互操作性,甚至轻松创建其他语言版本的nukak,如Python或Rust。
评论(0)