3.2 使用MongoDB重构

157 阅读3分钟

代码

删除src/model.ts和src/schema.ts。

import_map.json

在import_map.json中增加一条:

"deno_mongo_schema": "https://deno.land/x/deno_mongo_schema@v0.10.5/mod.ts"

我们将使用deno_mongo_schema来重构代码,具体API在详见其文档。

config.yaml

修改config.yaml,增加你的mongodb地址: image.png

globals.ts

src/globals.ts,在Config中增加刚才的配置: image.png

Schema

修改src/user/user.schema.ts,差异不大:

import { BaseSchema, Prop, Schema } from "deno_mongo_schema";

@Schema()
export class User extends BaseSchema {
  @Prop({
    index: true, // unique需要配置index使用,意思是设为索引
    unique: true,
  })
  name: string;

  ...
}

export type UserInfo = Required<User>;

src/session/session.schema.ts:

import {
  BaseSchema,
  Prop,
  Schema,
  SchemaFactory,
  VirtualTypeOptions,
} from "deno_mongo_schema";

@Schema()
export class Session extends BaseSchema {
  ...

  @Prop({
    index: true, // 增加索引标志
    expires: 60 * 60 * 24 * 7,
    default: Date.now,
  })
  expired?: Date;
}

src/posts/posts.schema.ts和src/comments/comments.schema.ts与之类似,额外需要删除定义的createTime和updateTime,因为已经定义在BaseSchema里了。

Service

与Schema类似,大部分代码都复用原来的,只修改一部分。

修改src/comments/comments.service.ts:

import { InjectModel, Model } from "deno_mongo_schema";

@Injectable()
export class CommentsService {
  create(params: CreateCommentDto) {
    return this.model.insertOne(params);
  }
}

修改src/user/user.service.ts:

import { InjectModel, Model } from "deno_mongo_schema";

@Injectable()
export class UserService {
  async getAll() {
    return this.userModel.findMany({});
  }

  async getUsersByIds(ids: string[]) {
    return this.userModel.findMany({
      _id: { $in: ids },
    });
  }

  async addUser(user: User) {
    const id = await this.userModel.insertOne(user);
    return id.toString();
  }
}

修改src/session/session.service.ts的findAll方法:

import { InjectModel, Model } from "deno_mongo_schema";

@Injectable()
export class SessionService {
  findAll(): Promise<Session[]> {
    return this.model.findMany();
  }
}

修改src/posts/posts.service.ts:

import { InjectModel, Model } from "deno_mongo_schema";

@Injectable()
export class PostsService {
  async save(params: CreatePostDto): Promise<string> {
    const id = await this.model.insertOne({
      ...params,
      pv: 0, // 去掉createTime和updateTime
    });
    return id.toString();
  }

  async findById(id: string, options: PopulateOptions = {}) {
    ...
    if (options.isWithUserInfo) {
      post.author = await this.userService.getUserById(post.userId) || null;
    }
    ...
  }

  async findAll(options: PopulateOptions = {}) {
    const posts = await this.model.findMany();
    ...
  }

  private async formatPosts(
    posts: Required<Post>[],
    options: PopulateOptions = {},
  ) {
    if (options.isWithUserInfo) {
      ...
      posts.forEach((post) => {
        post.author = users.find((user) => user.id === post.userId) || null;
      });
    }
    ...
  }
}

session.middleware.ts

修改src/session/session.middleware.ts,给findById增加一个catch。

为什么呢?因为mongodb根据id查找,如果没有找到,会返回null,但如果id非法(不是24位字符串),则会抛出异常。我们之前的session-id格式(nanoid)与mongodb的不一样,所以肯定会报错。

import { assert, Context, Factory, Next } from "oak_nest";
import { logger } from "../tools/log.ts";
import { SESSION_KEY } from "./session.schema.ts";
import { SessionService } from "./session.service.ts";

export async function SessionMiddleware(context: Context, next: Next) {
  ...
  if (sessionId) {
    session = await sessionService.findById(sessionId, true).catch((_err) =>
      null
    );
  }
  ...
}

app.module.ts

src/app.module.ts中需要初始化mongodb数据库:

import { MongoFactory } from "deno_mongo_schema";
import globals from "./globals.ts";

@Module({
  imports: [
    MongoFactory.forRoot(globals.db),
    ...
  ],
  ...
})
export class AppModule {}

验证

控制台

首先看到vscode控制台的变化,一开始打印了几句mongodb的连接与集合(也就是关系型数据库的表)创建的信息: image.png

mongo初始化

打开上节的adminMongo的页面,先配置我们的数据库: image.png 点击Connect,就能看到我们已经初始化的几个集合: image.png 打开users: image.png 点击这个Indexes,能看到这个集合的索引: image.png 可以看出,我们上面配置了name索引成功了。这样往这个集合里插入数据,如果遇到name重复的情况,就会抛出异常,保障唯一性。

操作页面

在浏览器访问http://localhost:8000,能看出页面空空如也。

先注册几个用户,再看users表的变化: image.png

再用这几个用户创建博客: image.png posts表中也有了数据: image.png 进行评论: image.png comments中也有了数据: image.png sessions表中也有一条数据: image.png

作业

我们使用deno_mongo_schema重构了现在的项目,数据从localStorage中迁移到了MongoDB中。看得出来,我们改动的代码不多,Controller层的代码没做任何改动,Service层也只是做了一些细节的修订,这也是分层的优势所在,上层不需要关注下层细节,将来你想把MongoDB替换成Redis、MySQL还是PostgreSQL都很方便。

上面session也使用了数据库,这样将来集群部署,也能解决session查找的问题。当然,因为session的请求频率非常高,最快肯定是内存,而内存中有不能持久化和集群后同步或分配等一系列复杂问题,目前通用的方法是使用redis数据库。这里我们图省事直接使用MongoDB了,读者在实际项目中需要注意。

deno_mongo_schema提供了集合间关联的快捷处理,详情参见deno.land/x/deno_mong…文档的virtual部分。下节我们将使用它优化我们的代码,你可以先尝试。