Loading

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)

参考

posted @ 2022-07-30 11:32  myEsn2E9  阅读(164)  评论(0编辑  收藏  举报