在 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:
@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;
}
}
DtoCategoryTree
Promise<DtoCategoryTree[]>
1
yuankui 29 天前
作为 Java 过来的
DTO 真的是恶习。 |
![]() |
2
zhennann OP @yuankui 其实跟标题说的一样,Java 也不能很好的支持自动推断生成 DTO ,所以,开发工作繁重。对于标准的后端 API 而言,DTO 甚至可以说是必须的。因为有了 DTO ,我们就可以支持传入参数的校验,也可以支持 Swagger 元数据的生成。
|
3
ByteCat 29 天前
直接 drizzle + drizzle-zod 就行了,比 prisma 好用
|