nestjs + mongoose discriminators(鉴别器架构)
简介
Discriminators
:鉴别器是一种模式继承机制。它们使您能够在同一个基础 MongoDB 集合之上拥有多个具有重叠模式的模型。
实现步骤
创建基类 Schema
// event.schema.ts
import { Document } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { ClickedLinkEvent } from './clicked-link-event.schema';
import { SignUpEvent } from './sign-up-event.schema';
import { adjustSchema } from '../../../../common/utils/schama.utils';
export type EventDocument = Event & Document;
/**
* Discriminators(鉴别器):鉴别器是一种模式继承机制。它们使您能够在同一个基础 MongoDB 集合之上拥有多个具有重叠模式的模型。
*
* mongoose 区分不同判别器模型的方式是通过“判别器键”,默认为 __t。
* Mongoose 将一个名为 __t 的字符串路径添加到您的模式中,用于跟踪该文档是哪个鉴别器的实例。
* 您还可以使用 discriminatorKey 选项来定义区分路径。
* https://docs.nestjs.com/techniques/mongodb#discriminators
*/
@Schema({ discriminatorKey: 'kind', timestamps: true })
export class Event {
@Prop({
type: String,
required: true,
enum: [ClickedLinkEvent.name, SignUpEvent.name],
})
kind: string;
@Prop({ type: Date, required: true })
time: Date;
}
export const EventSchema = adjustSchema(SchemaFactory.createForClass(Event));
创建基类(用于派生类继承)
// event-common-property.schema.ts
// 由于每个派生类都需要写基类中的公用字段,所以这里封装一个基类,子类直接继承,提高维护性
export class EventCommonProperty {
kind: string;
time: Date;
}
辅助函数
// schama.utils.ts
import mongoose from 'mongoose';
// 将 _id 序列化为 id,并禁用 versionKey
export function adjustSchema<TClass = any>(
schema: mongoose.Schema<TClass>,
): mongoose.Schema<TClass> {
return schema.set('toJSON', {
virtuals: true, // 序列化时,将 getter 也包含其中(因为 monngoose 默认添加了一个 id getter)
versionKey: false, // 禁用 versionKey,即 __v 文档修订版本
transform: (doc, result) => {
delete result._id; // 从结果中移除 _id 字段,因为 virtuals: true 会返回 id getter
},
});
}
创建派生类 Schema
// clicked-link-event.schema.ts
import { Document } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { EventCommonProperty } from './basic/event-common-property.schema';
export type ClickedLinkEventDocument = ClickedLinkEvent & Document;
@Schema()
export class ClickedLinkEvent extends EventCommonProperty {
@Prop({ type: String, required: true })
url: string;
}
export const ClickedLinkEventSchema =
SchemaFactory.createForClass(ClickedLinkEvent);
// sign-up-event.schema.ts
import { Document } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { EventCommonProperty } from './basic/event-common-property.schema';
export type SignUpEventDocument = SignUpEvent & Document;
@Schema()
export class SignUpEvent extends EventCommonProperty {
@Prop({ type: String, required: true })
user: string;
}
export const SignUpEventSchema = SchemaFactory.createForClass(SignUpEvent);
创建 Service
// event.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import {
Event,
EventDocument,
} from '../schemas/discriminator/basic/event.schema';
import {
ClickedLinkEvent,
ClickedLinkEventDocument,
} from '../schemas/discriminator/clicked-link-event.schema';
import {
SignUpEvent,
SignUpEventDocument,
} from '../schemas/discriminator/sign-up-event.schema';
@Injectable()
export class EventService {
constructor(
@InjectModel(ClickedLinkEvent.name)
private clickedLinkEventModel: Model<ClickedLinkEventDocument>,
@InjectModel(SignUpEvent.name)
private signUpEventModel: Model<SignUpEventDocument>,
@InjectModel(Event.name)
private eventModel: Model<EventDocument>,
) {}
findAll(kind?: string): Promise<(ClickedLinkEvent | SignUpEvent | Event)[]> {
// 注入的派生类并不会将 discriminatorKey 添加到查询条件中,只是说查询出来的数据拥有强类型,仅此而已
// if (kind === ClickedLinkEvent.name) {
// return this.clickedLinkEventModel.find({ kind }).exec();
// }
//
// if (kind === SignUpEvent.name) {
// return this.signUpEventModel.find({ kind }).exec();
// }
return this.eventModel.find(kind ? { kind } : null).exec();
}
async create(): Promise<void> {
await this.clickedLinkEventModel.create({
kind: ClickedLinkEvent.name,
time: new Date(),
url: 'http://baidu.com/',
});
await this.signUpEventModel.create({
kind: SignUpEvent.name,
time: new Date(),
user: 'myesn',
});
await this.eventModel.create({
kind: ClickedLinkEvent.name,
time: new Date(),
url: 'http://baidu2.com/',
});
await this.eventModel.create({
kind: SignUpEvent.name,
time: new Date(),
user: 'myesn2',
});
}
}
创建 Controller
// test-discriminators.controller.ts
import { Controller, Get, Post, Query } from '@nestjs/common';
import { EventService } from '../services/event.service';
@Controller('test-discriminators')
export class TestDiscriminatorsController {
constructor(private readonly eventService: EventService) {}
@Get()
findAllDiscriminators(@Query('kind') kind?: string) {
return this.eventService.findAll(kind);
}
@Post()
createDiscriminators() {
return this.eventService.create();
}
}
注册 Schema、Provider、Controller
// test.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { EventSchema } from './schemas/discriminator/basic/event.schema';
import {
ClickedLinkEvent,
ClickedLinkEventSchema,
} from './schemas/discriminator/clicked-link-event.schema';
import {
SignUpEvent,
SignUpEventSchema,
} from './schemas/discriminator/sign-up-event.schema';
import { EventService } from './services/event.service';
import { TestDiscriminatorsController } from './controllers/test-discriminators.controller';
@Module({
imports: [
MongooseModule.forFeature([
{
name: Event.name,
schema: EventSchema,
discriminators: [
{ name: ClickedLinkEvent.name, schema: ClickedLinkEventSchema },
{ name: SignUpEvent.name, schema: SignUpEventSchema },
],
},
]),
],
controllers: [TestDiscriminatorsController],
providers: [EventService],
})
export class TestModule {}
总结
Discriminators
的功能仅仅是简介中说明的,然后再加上开发时的强类型,其余的和普通的 Schema 无异。
注意:鉴别器架构只能修改部分 Schema 配置,比如配置 versionKey: false
会得到如下错误:
Can't customize discriminator option timestamps (can only modify toJSON, toObject, _id, id)