Prisma 不能优雅的支持 DTO,可以试试 Vona ORM

48 天前
 zhennann

在 Nodejs 生态中,Prisma 是一个非常流行的 ORM 库,支持 Typescript ,提供了非常友好的类型推断能力。但是,Prisma 却不能优雅的支持 DTO 。在与其他后端框架整合时,DTO 是进行参数验证、生成 Swagger 元数据的关键节点。如果不能像推断类型一样自动推断出 DTO ,那么,我们就仍然需要手工创建 DTO 。随着业务的增长,复杂的表间关系会让手工补充 DTO 的工作日益繁重。

而 Vona ORM 就提供了非常便利的工具,使我们可以非常直观的动态推断出 DTO ,就像推断类型一样,从而解放我们的双手,显著提升生产力。甚至可以说,能够自动推断 DTO ,为 Nodejs 后端框架打开了一扇窗。

限于篇幅,这里不展开讲解 Vona ORM 所有的知识点,而是以目录树为例,演示如何查询一棵目录树,以及如何动态生成 DTO ,并最终生成 Swagger 元数据。

1. 创建 Entity

在 VSCode 中,可以通过右键菜单Vona Create/Entity创建 Entity 的代码骨架:

@Entity('demoStudentCategory')
export class EntityCategory extends EntityBase {
  @Api.field()
  name: string;

  @Api.field(v.optional())
  categoryIdParent?: TableIdentity;
}

2. 创建 Model

在 VSCode 中,可以通过右键菜单Vona Create/Model创建 Model 的代码骨架:

import { EntityCategory } from '../entity/category.ts';

@Model({ entity: EntityCategory })
export class ModelCategory extends BeanModelBase<EntityCategory> {}

3. 创建树形结构

如果要创建一棵目录树,本质就是建立 Model 引用自身的递归结构。Vona ORM 同样支持 4 种关系:1 对 11 对多多对 1多对多。那么,在这里,我们就需要采用1 对多来创建目录的自身引用关系。

import { EntityCategory } from '../entity/category.ts';

@Model({
  entity: EntityCategory,
+ relations: {
+   children: $relation.hasMany(() => ModelCategory, 'categoryIdParent', {
+     autoload: true,
+     columns: ['id', 'name'],
+   }),
+ },
})
export class ModelCategory extends BeanModelBase<EntityCategory> {}

4. 创建 Controller

在 VSCode 中,可以通过右键菜单Vona Create/Controller创建 Controller 的代码骨架:

@Controller()
export class ControllerCategory extends BeanBase {}

接下来我们创建一个 Api ,用于获取目录树:

export class ControllerCategory extends BeanBase {
  @Web.get('getCategoryTree')
  async getCategoryTree() {
  }
}

5. 查询目录树

一般而言,我们还需要创建一个 Service ,从而实现以下调用链:Controller->Service->Model->操作数据库。为了简化起见,在这里,我们直接在 Controller 中调用 Model 方法:

export class ControllerCategory extends BeanBase {
  @Web.get('getCategoryTree')
  async getCategoryTree() {
    const tree = await this.scope.model.category.select({
      columns: ['id', 'name'],
    });
    return tree;
  }
}

由于前面我们设置 children 关系为autoload: true,因此,查询结果tree就是一棵完整的目录树。下面我们看一下tree的类型推断效果:

6. 自动推断 DTO

现在我们自动推断 DTO ,并且设为 API 的返回数据的类型:

export class ControllerCategory extends BeanBase {
  @Web.get('getCategoryTree')
+ @Api.body(v.array(v.object($Dto.get(() => ModelCategory, { columns: ['id', 'name'] }))))
  async getCategoryTree() {
    const tree = await this.scope.model.category.select({
      columns: ['id', 'name'],
    });
    return tree;
  }
}

同样,由于前面我们设置 children 关系为autoload: true,因此,$Dto.get生成的 DTO 就是一棵完整的目录树。下面我们看一下 API 的 Swagger 效果:

从示意图中,我们可以清晰的看到,这棵树引用的 children 类型是名称为demo-student.entity.category_2c7d642ee581efa300341e343180fbb0ecdc785d的动态 Entity 的数组,从而形成一种递归的引用关系。

7. 封装 DTO

虽然我们已经实现了预期的目标,但是 Vona ORM 提供的能力还没有结束。我们可以创建一个新的 DTO ,将前面的代码$Dto.get(() => ModelCategory, { columns: ['id', 'name'] })封装起来,从而用于其他地方:

在 VSCode 中,可以通过右键菜单Vona Create/Dto创建 DTO 的代码骨架:

@Dto()
export class DtoCategoryTree {}

然后我们通过继承机制来封装 DTO:

@Dto()
export class DtoCategoryTree 
+ extends $Dto.get(() => ModelCategory, { columns: ['id', 'name'] }) {}

现在,我们再使用新创建的 DTO 来改造前面的 API 代码:

export class ControllerCategory extends BeanBase {
  @Web.get('getCategoryTree')
+ @Api.body(v.array(v.object(DtoCategoryTree)))
+ async getCategoryTree(): Promise<DtoCategoryTree[]>{
    const tree = await this.scope.model.category.select({
      columns: ['id', 'name'],
    });
    return tree;
  }
}
1469 次点击
所在节点    Node.js
4 条回复
yuankui
47 天前
作为 Java 过来的

DTO 真的是恶习。
zhennann
47 天前
@yuankui 其实跟标题说的一样,Java 也不能很好的支持自动推断生成 DTO ,所以,开发工作繁重。对于标准的后端 API 而言,DTO 甚至可以说是必须的。因为有了 DTO ,我们就可以支持传入参数的校验,也可以支持 Swagger 元数据的生成。
ByteCat
47 天前
直接 drizzle + drizzle-zod 就行了,比 prisma 好用
zhennann
46 天前
@ByteCat drizzle-zod 貌似只能基于 table 模型生成 zod schema ,不支持基于 relations 生成 zod schema 。就比如此文中自动生成目录树的 DTO ,drizzle-zod 该如何做呢?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1150216

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX