MongoDB 支持具有六边形架构的 NestJS Boilerplate

我们创造了 NestJS 样板 2020年8月,此后我们一直致力于其优化和改进。 NestJS 样板是一个项目,其中包含所有必要的库和解决方案,例如身份验证、邮件发送等,可使用经典的 REST API 方法快速启动您的项目。 目前该样板在Github上已有1.8K star,并得到了开发者社区的认可和支持。 最近我们还发布了新的 React 的前端样板 与后端实现非常兼容,所以请检查一下。


包含 Mongo 支持的动机

PostgreSQL 支持最初包含在样板中,因为它的可靠性、数据完整性和活跃的社区。 但对于需要高速处理大型数据集和高可扩展性的项目,MongoDB 通常是更好的选择。 因此,我们希望将 MongoDB 支持集成到我们的项目中。 此外,我们还收到了来自使用此样板的社区成员和同事的大量请求,要求包含 NoSQL DB 支持。

社区请求支持 MongoDB

现在已经完成了,开发人员可以在面向文档的数据库 MongoDB 和关系数据库 PostgreSQL 之间进行选择。

现在让我们弄清楚在建立新项目时使用什么更好。 当然,问题不是哪个数据库更好,因为这两个数据库都很优秀,这完全取决于应用程序的范围和目标。 让我们深入了解细节。

  • 如果您需要一个使用复杂 SQL 请求并与大多数支持关系表结构的应用程序兼容的关系数据库,那么最好选择 PostgreSQL。
  • 对于需要高安全性和高 ACID 合规性的场景,那么 PostgreSQL 是最佳解决方案。
  • 如果您需要一个可靠的工具来处理处理多结构、快速变化的数据的应用程序中的复杂事务和分析,那么 MongoDB 是您项目的不错选择。
  • 如果您运行的应用程序需要扩展并且需要跨区域分布以实现数据局部性或数据主权,MongoDB 的横向扩展架构将自动满足这些需求。

如果您需要了解更多有关 MongoDB 与 PostgreSQL 比较的信息,我建议您查看 本文

为了提供良好的抽象级别并简化 MongoDB 的工作,我们使用 猫鼬 – 对象数据建模 (ODM) 库。 它允许开发人员使用基于模式的方法定义他们的数据模型,并提供一组丰富的功能来简化使用 MongoDB 的过程。 除了支持开箱即用的基本 CRUD 操作和查询功能之外,Mongoose 还提供了一组更丰富的用于 MongoDB 的功能,例如中间件功能、虚拟属性、查询构建器和模式验证。 它允许开发人员定义数据的结构,包括每个字段的类型,并指定验证规则以确保数据的一致性和完整性。

通过 Mongoose 管理的 Node 和 MongoDB 之间的对象映射
通过 Mongoose 管理的 Node 和 MongoDB 之间的对象映射


六边形架构的实现

为了允许应用程序独立于其终端设备和数据库统一管理批处理执行场景,使用了 Alistair Cockburn 引入的六边形软件架构(又名端口和适配器架构)。 在 他的文章,他强调用户界面和数据库与应用程序交互的方式没有太大区别,因为它们都是外部连接,可以与类似组件互换,并以相同的方式与应用程序交互。 因此,我们在项目中采用了这种架构方式,它可以让我们封装数据源的实现细节,从而实现样板中对2种数据库的支持。
让我们仔细看看实施情况。 首先,我们在中创建 User 实体 users/domain 目录。

export class User {
  id: number | string;
  email: string | null;
  password?: string;
  firstName: string | null;
  lastName: string | null;

  // ...
}
进入全屏模式

退出全屏模式

然后我们创建一个名为的端口 UserRepository

export abstract class UserRepository {
  abstract create(
    data: Omit<User, 'id'>,
  ): Promise<User>;
  abstract findOne(fields: EntityCondition<User>): Promise<NullableType<User>>;
}
进入全屏模式

退出全屏模式

users/infrastructure/persistence/relational/repositories 我们实现 UserRepository 来使用 TypeORM。

@Injectable()
export class UsersRelationalRepository implements UserRepository {
  constructor(
    @InjectRepository(UserEntity)
    private readonly usersRepository: Repository<UserEntity>,
  ) {}

  async create(data: User): Promise<User> {
    const persistenceModel = UserMapper.toPersistence(data);
    const newEntity = await this.usersRepository.save(
      this.usersRepository.create(persistenceModel),
    );
    return UserMapper.toDomain(newEntity);
  }

  async findOne(fields: EntityCondition<User>): Promise<NullableType<User>> {
    const entity = await this.usersRepository.findOne({
      where: fields as FindOptionsWhere<UserEntity>,
    });

    return entity ? UserMapper.toDomain(entity) : null;
  }
}
进入全屏模式

退出全屏模式

我们创建了一个用于使用 TypeORM 的模块 users/infrastructure/persistence/relational

@Module({
  imports: [TypeOrmModule.forFeature([UserEntity])],
  providers: [
    {
      provide: UserRepository,
      useClass: UsersRelationalRepository,
    },
  ],
  exports: [UserRepository],
})
export class RelationalUserPersistenceModule {}
进入全屏模式

退出全屏模式

现在我们对 Mongoose 做同样的事情。

@Injectable()
export class UsersDocumentRepository implements UserRepository {
  constructor(
    @InjectModel(UserSchemaClass.name)
    private readonly usersModel: Model<UserSchemaClass>,
  ) {}

  async create(data: User): Promise<User> {
    const persistenceModel = UserMapper.toPersistence(data);
    const createdUser = new this.usersModel(persistenceModel);
    const userObject = await createdUser.save();
    return UserMapper.toDomain(userObject);
  }

  async findOne(fields: EntityCondition<User>): Promise<NullableType<User>> {
    if (fields.id) {
      const userObject = await this.usersModel.findById(fields.id);
      return userObject ? UserMapper.toDomain(userObject) : null;
    }

    const userObject = await this.usersModel.findOne(fields);
    return userObject ? UserMapper.toDomain(userObject) : null;
  }
}
进入全屏模式

退出全屏模式

以及用于使用 MongoDB 的模块。

@Module({
  imports: [
    MongooseModule.forFeature([
      { name: UserSchemaClass.name, schema: UserSchema },
    ]),
  ],
  providers: [
    {
      provide: UserRepository,
      useClass: UsersDocumentRepository,
    },
  ],
  exports: [UserRepository],
})
export class DocumentUserPersistenceModule {}
进入全屏模式

退出全屏模式

之后,我们连接用于使用 Mongoose (DocumentUserPersistenceModule) 或 TypeORM (RelationalUserPersistenceModule) 的模块 users/users.module.ts 基于 ENV 配置。

const infrastructurePersistenceModule = (databaseConfig() as DatabaseConfig)
  .isDocumentDatabase
  ? DocumentUserPersistenceModule
  : RelationalUserPersistenceModule;

@Module({
  imports: [infrastructurePersistenceModule, FilesModule],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService, infrastructurePersistenceModule],
})
export class UsersModule {}
进入全屏模式

退出全屏模式

然后在UserService中我们可以访问UserRepository,nestjs会根据ENV配置设置了解使用哪个数据库。

@Injectable()
export class UsersService {
  constructor(
    private readonly usersRepository: UserRepository,
  ) {}
}
进入全屏模式

退出全屏模式

完整的实现可以找到 这里


MongoDB 架构

Schema 是根据 MongoDB 的最佳实践构建的,以实现最佳性能和可扩展性。 NoSQL 数据库的架构设计与关系数据库不同。 区别之一是,在关系数据库中,我们可以选择将模式简化为正常形式以避免重复等。而对于 NoSQL,我们可以复制数据以避免“连接”,因此在执行过程中将实现最佳性能指标。数据采样。 让我们以样板为例,看看有什么区别。 PostgreSQL 的数据库架构如下所示:

PostgreSQL 的数据库架构

以及数据表的示例:

PostgreSQL 数据表示例

说到MongoDB,那么我们不能在这里转移PostgreSQL的设计经验,即创建4个集合用户、文件(用于照片)、角色、状态并在用户集合中存储到其他集合的链接以及在使用聚合进行数据采样期间( $lookup) 附加额外的数据,因为这会影响性能​(阅读有关连接比较的更多信息 在本文中,虽然2020年听起来很老,但它仍然是实际的)。 该计划应该是什么样子? 一切都非常简单:所有数据必须存储在一个集合中:

MongoDB 架构和数据集示例

现在,当我们获取用户时,我们将不需要提出额外的请求来获取有关用户的照片、角色和状态的数据,因为所有数据都已经存储在用户的集合中,事实上,因此生产力将会提高。

要了解有关设计 MongoDB 模式的更多信息,我建议查看 本文


如何将 NestJS 样板与 Mongoose 一起使用

为了舒适的开发(MongoDB + Mongoose),您必须克隆存储库,转到文件夹 my-app/ 并复制 env-example-document 作为 .env

cd my-app/
cp env-example-document .env

改变 DATABASE_URL=mongodb://mongo:27017DATABASE_URL=mongodb://localhost:27017

运行附加容器:
docker compose -f docker-compose.document.yaml up -d mongo mongo-express maildev

安装依赖
npm install

运行迁移
npm run migration:run

运行种子
npm run seed:run:document

在开发模式下运行应用程序
npm run start:dev

就是这样。

如果我们谈论所选数据库对前端应用程序的影响,特别是 广泛的 React 样板,我们也保持最新,并且它与当前讨论的效果很好 NestJS 样板,所以不会影响他们的互动。

前端 React 样板

无论您选择使用哪种数据库 – PostgreSQL 或 MongoDB(它们都很棒),选择应该取决于整个项目,在我们的样板中您可以选择)因此,如果您发现它有用但不喜欢,欢迎尝试它忘记点击图书馆的星星⭐。

本文的完整学分 弗拉德·谢波廷埃琳娜·弗拉森科 🇺🇦

Leave a Reply

Your email address will not be published. Required fields are marked *

近期新闻​

编辑精选​