高含有高分子成分的 Prisma 2 回归了
首先
因为前不久Prisma 2的预览版被宣布出来,所以我闲来无事就调查了一下。
由于仍处于预览阶段,实施和API可能会发生重大变更的可能性。
Prisma 1 对比 Prisma 2
Prisma 1 是什么来着?
直接使用graphcool的后端部分切割出来的框架是Prisma,简单来说,Prisma就是GraphQL as a Service。
从此篇文章(https://www.prisma.io/blog/prisma-raises-4-5m-to-build-the-graphql-data-layer-for-all-databases-663484df0f60)可以看出,Prisma已经筹集到450万美元的资金,旨在构建适用于所有数据库的GraphQL数据层。
而且,正如prisma-最快的GraphQL服务器实现所述,
具有GraphQL形式的ORM
带有MySQL/Postgre迁移助手
从模型定义自动生成索引
自动生成CRUD操作
具有这样的特征。
在Prisma中,我们使用GraphQL SDL来定义数据模型,
type User {
id: ID! @id
email: String @unique
name: String!
posts: [Post!]!
}
type Post {
id: ID! @id
title: String!
published: Boolean! @default(value: false)
author: User @relation(link: INLINE)
}
$ prisma deploy
当您执行此命令时,将自动生成上述模型的CRUD,并可以作为GraphQL API执行。
Prisma 2带来了很大的改变。
下图(引用自Preview的公告博客)简明地展示了1.x和2.x之间的差异。
在Prisma 1中,需要准备一个名为Prisma Server的服务器,其中集成了执行GraphQL <-> RDB转换的ORM部分和迁移功能。
正如图上所示,Prisma 2中已经取消了Prisma Server,而是直接由应用程序的API服务器与数据库进行交互。
顺便说一下,这个”API服务器”并不一定需要是GraphQL API。它可以是REST API,也可以是gRPC或者Thrift。此外,后面会提到,使用Prisma 2与数据库交互时也不一定需要使用GraphQL。
总之,Prisma 2已经完全与GraphQL分离,成为了一个ORM。
光子和电梯
刚才的图中也已经出现了,Prisma 2由两个重要组件构成。它们是Photon和Lift。
-
- Photon: ORM本体。現状はMySQL/PostgreSQL/SQLiteのみに対応していますが、MongoDBへの対応も計画されているとのとこと
- Lift: マイグレーションエンジン。いわゆるRailsのマイグレーションとかと同じような位置づけ
在Prisma 2中,基于SDL的模型定义仍然存在。我们将创建类似GraphQL SDL的定义体,如下所示。这将成为所有内容的基础。
datasource db {
provider = "sqlite"
url = "file:dev.db"
default = true
}
model User {
id String @default(cuid()) @id @unique
email String @unique
name String?
posts Post[]
}
Photon的CLI根据这个定义体,可以生成可以从应用程序源代码中导入的DAO代码。目前只实现了JavaScript/TypeScript的代码输出,但是Go语言似乎也包含在里程碑计划中。对于TypeScript来说,还包含模型的类型信息,这样在编写代码时可以进行补全和错误检查,非常方便。
Lift根据Prisma文件生成迁移。当执行Lift的CLI时,会在migrations目录下创建带有时间戳前缀的目录,并逐步应用迁移到数据库中,可以顺利应用迁移。如果你有接触过Rails的迁移,应该很容易理解。
与GraphQL兼容性
正如上述所提到的,Prisma 2专注于ORM层。与Prisma 1相比,它与模型定义到GraphQL的CRUD操作之间的无缝创建具有显著差异。
关于这个问题,在官方的常见问题解答中明确提到,如下所示进行了回答。
随着Prisma 2,Prisma的查询引擎不再公开符合规范的GraphQL端点,因此不再官方支持在Prisma 2中使用模式委派和GraphQL绑定。要使用Prisma 2构建GraphQL服务器,请务必查看GraphQL Nexus及其nexus-prisma集成。GraphQL Nexus提供了一种基于代码和类型安全的方式来构建可扩展的GraphQL服务器。
「虽然 Prisma 2 不支持 GraphQL binding,但是我认为如果使用 GraphQL Nexus(也是 Prisma 组织下的工具),就可以相对容易地实现了。所以可能更多是关于这些其他工具链的讨论。」
项目的流程
由于翻译角度的原因,我进行了一些笔记,但只是简单地抄写了官方示例加上一点补充,所以请无视它们也没有关系。
安装Prisma 2 CLI。
$ npm i -g prisma2
创建项目
$ prisma2 init prisma2-nexus-example
-
- sqlite
- GraphQL APIのboilerplateを利用
完成无事项目后,您可以使用锅炉板设置的模型定义文件进行查看。
datasource db {
provider = "sqlite"
url = "file:dev.db"
default = true
}
generator photon {
provider = "photonjs"
}
generator nexus_prisma {
provider = "nexus-prisma"
}
model User {
id String @default(cuid()) @id @unique
email String @unique
name String?
posts Post[]
}
model Post {
id String @default(cuid()) @id @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
published Boolean
title String
content String?
author User?
}
数据库的设置
根据上述的模型定义,在Lift中执行迁移,并准备数据库。
首先,需要从当前模型定义中创建一个包括CREATE TABLE等操作的迁移。可以使用以下命令执行该操作。
$ prisma2 lift save
当您输入一个适当的名称(例如init),将会以时间戳-名称的形式创建在下图所示的migrations目录下。
- prisma/
|- migrations/
|- 20190808024227-init/
| datamodel.prisma
| README.md
| steps.json
| lift.lock
| schema.prisma
您可以从README.md中查看此迁移将执行的TDL以及原始模型定义的差异信息。
请使用以下命令来应用已创建的迁移。
$ prisma2 lift up
顺便提一下,回滚操作可以使用 prisma2 lift down。
光子的准备
接下来是Photon端。通过执行以下命令,将生成Photon的TypeScript客户端。
$ prisma2 generate
话虽这么说,Photon的客户端代码会在node_modules/@generated/photon目录下生成。
一开始看到时我还纳闷,为什么要放在node_modules下面呢…但是如果放在src目录下,生成的代码会在tsserver中展开,这样在更新和重新生成的时候,tsserver中的文件还是旧的状态,不方便进行补全和错误检查等操作。
因为这个原因,生成命令被指定为package.json中的postinstall操作。
光子的运行
既然已经准备好了数据库和Photon客户端,让我们来执行Photon吧。
因为我们选择的这个模版包含了在seed.ts中创建用户的示例,所以我们将执行该示例。
import Photon from '@generated/photon'
const photon = new Photon()
async function main() {
const user1 = await photon.users.create({
data: {
email: 'alice@prisma.io',
name: 'Alice',
posts: {
create: {
title: 'Join us for Prisma Day 2019 in Berlin',
content: 'https://www.prisma.io/day/',
published: true,
},
},
},
})
const user2 = await photon.users.create({
// 中略
})
console.log({ user1, user2 })
}
main()
.catch(e => console.error(e))
.finally(async () => {
await photon.disconnect()
})
Photon.users.create(…)这部分是Photon的执行,使用TypeScript补全非常高兴。
$ yarn run ts-node prisma/seed.ts
当查看数据库时,应该能够确认通过 Photon 创建了记录并存入了 User 表中。
$ sqlite3 prisma/dev.db
sqlite> select name from User;
Alice
Bob
应用程序执行
也许与Prisma 2无关,但由于选择了作为GraphQL API服务器的基本模板,因此只需要在当前状态下运行yarn start即可执行。
顺便提一句,与GraphQL相关的技术栈如下所示。
-
- APIサーバー層: graphql-yoga
Resolverフレームワーク層:GraphQL Nexus
总之,GraphQL相关部分只是最初准备好的代码在运行而已。如果非要说的话,只能说resolver在引用Photon的客户端代码而已。
模型的编辑与应用程序的实现更改
我尝试实施了“在表上添加列并实现用于设置该列的GraphQL变更”这个任务。
在Post模型中添加一个名为category的字段。
model Post {
# ...略
category String?
}
使用 Lift 创建和执行迁移。
$ prisma2 lift save
$ prisma2 lift up
光子客户端也会重新生成。
$ prisma2 generate
我們需要修改GraphQL Nexus的程式碼,並更改Post類型的定義。
export const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.createdAt()
t.model.updatedAt()
t.model.title()
t.model.content()
t.model.published()
t.model.author()
t.model.category() // これが追加する部分
},
})
关于上述的 t.model.category() 这部分,通过 nexus-prisma 插件,Prisma 2 生成了用于将 Photon 模型信息作为 GraphQL 的类型定义进行使用的接口,并且可以通过 Nexus 方面来引用该类型,从而实现一些便捷的功能。说实话,我还不太了解 Prisma 2 和 GraphQL Nexus 之间的关系。
解释mutation的resolver只需将其作为Photon Client编写即可,这样更易理解。
t.field('setCategory', {
type: 'Post',
args: {
id: idArg(),
category: stringArg(),
},
resolve: async (_, { id, category }, ctx) => {
return await ctx.photon.posts.update({
where: { id },
data: {
category,
},
})
},
})
另外,如果你有兴趣,可以在GitHub的https://github.com/Quramy/prisma2-nexus-example中找到在示例中的工作,并进行推送。这个提交对应于“模型更改+实现修正”的工作。
个人感受
作为ORM
我之前已经多次提到过,Prisma 2基本上只是一个ORM。
在JavaScript/TypeScript的ORM上下文中,我们在比较TypeORM和其他类似工具时,讨论的重点就是它们之间的区别。
我更喜欢Prisma 2,它只使用了简单的对象,相较于倾向于Hibernate/ Active Record风格的TypeORM。
对于Prisma 2的核心由Rust实现这一事实,一方面,我也有一些担忧,例如,当出现问题时,我是否能够自行追踪代码?另外,作为开源软件,它是否能够得到有效的持续维护?
作为GraphQL的工具
最初にPrisma 2のプレビューを読もうと思ったのは、GraphQLの文脈であるからですが、GraphQL APIサーバーのバックエンドとして考えると、使い方があまり明確ではなく、どうなのかよくわかりません。
通过松散耦合化努力,可以肯定地说相较于Prisma 1.x时期,开发工作变得更加繁琐了,同时虽然Nexus被推荐使用且属于prisma组织的存储库,但我还是有些担心核心贡献者并非来自Prisma的这一点。
另外,当我同时接触Nexus和Prisma 2时,下面列举的哲学差异引起了我的一些关注。
-
- Prisma 2: PSL使った定義体ドリブンなフレームワーク
- Nexus: GraphQL type定義をコードベースで記述し、最後にGraphQL SDLが自動で出力されるフレームワーク
如果是同样的prisma系工具,结合类似graphqlgen的SDL优先库可能更加符合需求吧?我认为对于简单的CRUD操作来说,使用这种方式定义会变得相当冗长(也有个人不太喜欢在实现中逐渐添加注解的框架的原因)。
你可以自己搭建服务器,也可以使用名为Prisma cloud的托管服务。
准确来说,它被称为PSL(Prisma Schema Language)。
参考链接:https://github.com/prisma/prisma2/blob/master/docs/faq.md#does-photon-support-graphql-schema-delegation-and-graphql-binding
参考链接:https://github.com/prisma/photonjs/issues/77#issuecomment-508162695 大体是这样的,如果想要tsserver能够正确支持这个,就需要创建插件,向DocumentRegistory适当通知更新并且清空ScriptVersionCache,等等…