
在 Nodejs 生态中,Prisma 是一个非常流行的 ORM 库,支持 Typescript ,提供了非常友好的类型推断能力。但是,Prisma 却不能优雅的支持 DTO 。在与其他后端框架整合时,DTO 是进行参数验证、生成 Swagger 元数据的关键节点。如果不能像推断类型一样自动推断出 DTO ,那么,我们就仍然需要手工创建 DTO 。随着业务的增长,复杂的表间关系会让手工补充 DTO 的工作日益繁重。
而 Vona ORM 就提供了非常便利的工具,使我们可以非常直观的动态推断出 DTO ,就像推断类型一样,从而解放我们的双手,显著提升生产力。甚至可以说,能够自动推断 DTO ,为 Nodejs 后端框架打开了一扇窗。
限于篇幅,这里不展开讲解 Vona ORM 所有的知识点,而是以目录树为例,演示如何查询一棵目录树,以及如何动态生成 DTO ,并最终生成 Swagger 元数据。
在 VSCode 中,可以通过右键菜单Vona Create/Entity创建 Entity 的代码骨架:
@Entity('demoStudentCategory') export class EntityCategory extends EntityBase { @Api.field() name: string; @Api.field(v.optional()) categoryIdParent?: TableIdentity; } 在 VSCode 中,可以通过右键菜单Vona Create/Model创建 Model 的代码骨架:
import { EntityCategory } from '../entity/category.ts'; @Model({ entity: EntityCategory }) export class ModelCategory extends BeanModelBase<EntityCategory> {} 如果要创建一棵目录树,本质就是建立 Model 引用自身的递归结构。Vona ORM 同样支持 4 种关系:1 对 1、1 对多、多对 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> {} 在 VSCode 中,可以通过右键菜单Vona Create/Controller创建 Controller 的代码骨架:
@Controller() export class ControllerCategory extends BeanBase {} 接下来我们创建一个 Api ,用于获取目录树:
export class ControllerCategory extends BeanBase { @Web.get('getCategoryTree') async getCategoryTree() { } } 一般而言,我们还需要创建一个 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; } } this.scope取得 Category Model ,然后调用 select 方法由于前面我们设置 children 关系为autoload: true,因此,查询结果tree就是一棵完整的目录树。下面我们看一下tree的类型推断效果:


现在我们自动推断 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 的数组,从而形成一种递归的引用关系。
虽然我们已经实现了预期的目标,但是 Vona ORM 提供的能力还没有结束。我们可以创建一个新的 DTO ,将前面的代码$Dto.get(() => ModelCategory, { columns: ['id', 'name'] })封装起来,从而用于其他地方:
在 VSCode 中,可以通过右键菜单Vona Create/Dto创建 DTO 的代码骨架:
@Dto() export class DtoCategoryTree {} 然后我们通过继承机制来封装 DTO:
@to() 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; } } DtoCategoryTreePromise<DtoCategoryTree[]> 1 yuankui 118 天前 作为 Java 过来的 DTO 真的是恶习。 |
2 zhennann OP @yuankui 其实跟标题说的一样,Java 也不能很好的支持自动推断生成 DTO ,所以,开发工作繁重。对于标准的后端 API 而言,DTO 甚至可以说是必须的。因为有了 DTO ,我们就可以支持传入参数的校验,也可以支持 Swagger 元数据的生成。 |
3 ByteCat 118 天前 直接 drizzle + drizzle-zod 就行了,比 prisma 好用 |