Prisma,我觉得也不是那么的香...

4,777 阅读4分钟

我们项目主要使用的是 mongodb ,目前使用的 ORM 框架是 TypeORM。但是 TypeORM 的更新速度和对于 MongoDB 的支持实在感觉有点难受,于是之前抽空尝试了一下火热的 Prisma,写了一套 CURD 之后感觉有些五味杂陈...

Prisma 诸如类型安全,Prisma Studio 等等新功能可以阅读社区中的文章了解,本文还是想写出一些略微不便之处。

Prisma 与 TypeORM 的对比可以参考:

  1. prisma.yoga/concepts/mo…
  2. juejin.cn/post/732320…

仓库 issues 回应速度较慢

将这个问题写在第一是因为下面列出的所有问题我都在 issue 中查找到了社区反馈,但是始终没有官方的解决方案。issue open 时间极其长,feedback 不断叠加。

image.png

image.png

image.png

Mongodb 需要使用副本集模式

www.prisma.io/docs/orm/ov…

对于 Mongodb sharding 模式我没有进行实际测试,仅依靠 issue 描述进行判断,可能至当前版本时已经支持。

github.com/prisma/pris… github.com/prisma/pris…

Prisma 如果不将 MongoDB 配置为副本集模式,在运行 prisma gengrate 时会提示

Transactions are not supported by this deployment

我在开发过程中通常是通过 docker 创建一个独立的 mongodb,在 docker mongodb 中创建副本集的命令是

docker run --name mongodb -d -p 27017:27017 mongo:latest mongod --replSet rs0
docker exec -d mongodb mongosh --eval \
    "rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'localhost:27017'}]})"

在生产环境中,配置副本集链接字符串有两种方法:

  1. 将副本集中的所有节点都写入链接字符串中
mongodb://username:password@host1,host2,host3/mydatabase?replicaSet=myReplicaSetName

将所有节点都写入会造成链接字符串极其长,可维护性变差

  1. 使用 mongodb+srv 方式进行链接
mongodb+srv://username:password@your-cluster-hostname/databaseName?replicaSet=yourReplicaSetName

使用 DNS SRV 记录可能需要自行维护记录解析的变更,并且在记录变更后由于 DNS 解析缓存的原因也可能造成无法及时切换。

schema.prisma 文件无法拆分,所有的模型 All in one

github.com/prisma/pris…

Prisma 需要在 schema.prisma 中声明 client-provier 和 datasouce 以及各种模型,Prisma 只会读取一个单一的 xx.prisma 文件。假如项目中存在多个数据表,可以预想到文件的行数会变得极其长,在一个极其长的文件中上下滑动寻找表结构并修改的体验我觉得并不是很好。

反观 TypeORM 为单独定义 Entity 并手动导入使用,倒也就可以单独对某个表进行修改,并且在 git diff 时也很好观察。

社区也有了一些解决方法,有自行实现了 import 关键字的,也有通过编辑器插件实现注释大纲的。我最后选择了 import 方案。

  1. 编辑器插件实现注释大纲

github.com/prisma/pris…

github.com/akirarika/c…

好处是侵入性小,只需要安装一个独立的 vscode 插件即可,不影响官方的编辑器插件

image.png

  1. prisma-import

github.com/ajmnz/prism…

可以将每个 model 都拆分到不同的 .prisma 文件中,通过 import 关键字导入。目录结构可以如下所示:

prisma
 ├── schema.prisma
 └── src
     ├── index.prisma
     └── model
         ├── comment.model.prisma
         ├── post.model.prisma
         └── user.model.prisma

schema.prismaprisma-import 最终会覆写的文件,不应在此文件中手动书写任何配置。

index.prisma 文件中包含了 client-provider、datasouce 等全局配置,以及每个 model 的 import

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mongodb"
  url      = env("DATABASE_URL")
}

import { User } from "model/user.model"
import { Post } from "model/post.model"
import { Comment } from "model/comment.model"

*.model.prisma 文件中按照官方语法定义 model 即可,如果有需要进行关联的表也可以通过 import 导入。

import { Post } from "./post.model"

model User {
  id      String   @id @default(auto()) @map("_id") @db.ObjectId
  email   String   @unique
  name    String
  address Address?
  posts   Post[]
}

type Address {
  street String
  city   String
  state  String
  zip    Int
}

最终通过 cli 命令进行覆写即可

prisma-import --force --schemas prisma/src/index.prisma --schemas prisma/src/model/*

覆写后的 schema.prisma 文件如下:

//
// Autogenerated by `prisma-import`
// Any modifications will be overwritten on subsequent runs.
//

//
// index.prisma
//

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mongodb"
  url      = env("DATABASE_URL")
}

//
// comment.model.prisma
//

model Comment {
  id      String @id @default(auto()) @map("_id") @db.ObjectId
  comment String
  post    Post   @relation(fields: [postId], references: [id])
  postId  String @db.ObjectId
}

但这与官方的语法相悖,使用官方的编辑器插件高亮会各种飘红。prisma-import 作者只开发了 vscode 的编辑器插件,Webstorm 的体验感相对较差。并且需要禁用官方的编辑器插件以避免冲突,如果之后官方支持了新的语法或关键字可能插件不能及时支持。

  • WebStorm

image.png

  • VSCode

image.png

findAndCount 没有官方的支持,需要安装插件

github.com/prisma/pris…

可以通过安装插件或手动运行 count 查询实现相同的效果。

我翻了一下 typeorm 的源码,发现他们也是同时运行 find & count 实现的 findAndCount

  • 使用插件

github.com/deptyped/pr…

插件可以支持普通的 offset & limit 分页和游标分页。

import { pagination } from 'prisma-extension-pagination';

new PrismaClient({
  datasourceUrl: env.DATABASE_URL,
  log: [{ level: 'query', emit: 'event' }],
}).$extends(pagination())

paginate().withPages() 中可以解构出 meta

const [users, meta] = await prisma.user.paginate().withPages({
  page,
  limit: pageSize,
  includePageCount: true,
});

return ({
    pagination: {
      page: meta.currentPage,
      pageSize: ctx.request.query.pageSize,
      total: meta.totalCount,
      pageCount: meta.pageCount,
    }
})

最后叠个甲

本文主要记录对于 Prisma 的一些不便之处以及对应的解决方法,其中有些问题只通过阅读 issue,没有实际搭建环境测试,可能存在错误。如果有更好的解决方法或文中存在错误,敬请指正,谢谢(鞠躬)