我们创造了 NestJS 样板 2020年8月,此后我们一直致力于其优化和改进。 NestJS 样板是一个项目,其中包含所有必要的库和解决方案,例如身份验证、邮件发送等,可使用经典的 REST API 方法快速启动您的项目。 目前该样板在Github上已有1.8K star,并得到了开发者社区的认可和支持。 最近我们还发布了新的 React 的前端样板 与后端实现非常兼容,所以请检查一下。
包含 Mongo 支持的动机
PostgreSQL 支持最初包含在样板中,因为它的可靠性、数据完整性和活跃的社区。 但对于需要高速处理大型数据集和高可扩展性的项目,MongoDB 通常是更好的选择。 因此,我们希望将 MongoDB 支持集成到我们的项目中。 此外,我们还收到了来自使用此样板的社区成员和同事的大量请求,要求包含 NoSQL DB 支持。
现在已经完成了,开发人员可以在面向文档的数据库 MongoDB 和关系数据库 PostgreSQL 之间进行选择。
现在让我们弄清楚在建立新项目时使用什么更好。 当然,问题不是哪个数据库更好,因为这两个数据库都很优秀,这完全取决于应用程序的范围和目标。 让我们深入了解细节。
- 如果您需要一个使用复杂 SQL 请求并与大多数支持关系表结构的应用程序兼容的关系数据库,那么最好选择 PostgreSQL。
- 对于需要高安全性和高 ACID 合规性的场景,那么 PostgreSQL 是最佳解决方案。
- 如果您需要一个可靠的工具来处理处理多结构、快速变化的数据的应用程序中的复杂事务和分析,那么 MongoDB 是您项目的不错选择。
- 如果您运行的应用程序需要扩展并且需要跨区域分布以实现数据局部性或数据主权,MongoDB 的横向扩展架构将自动满足这些需求。
如果您需要了解更多有关 MongoDB 与 PostgreSQL 比较的信息,我建议您查看 本文。
为了提供良好的抽象级别并简化 MongoDB 的工作,我们使用 猫鼬 – 对象数据建模 (ODM) 库。 它允许开发人员使用基于模式的方法定义他们的数据模型,并提供一组丰富的功能来简化使用 MongoDB 的过程。 除了支持开箱即用的基本 CRUD 操作和查询功能之外,Mongoose 还提供了一组更丰富的用于 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 的数据库架构如下所示:
以及数据表的示例:
说到MongoDB,那么我们不能在这里转移PostgreSQL的设计经验,即创建4个集合用户、文件(用于照片)、角色、状态并在用户集合中存储到其他集合的链接以及在使用聚合进行数据采样期间( $lookup) 附加额外的数据,因为这会影响性能(阅读有关连接比较的更多信息 在本文中,虽然2020年听起来很老,但它仍然是实际的)。 该计划应该是什么样子? 一切都非常简单:所有数据必须存储在一个集合中:
现在,当我们获取用户时,我们将不需要提出额外的请求来获取有关用户的照片、角色和状态的数据,因为所有数据都已经存储在用户的集合中,事实上,因此生产力将会提高。
要了解有关设计 MongoDB 模式的更多信息,我建议查看 本文。
如何将 NestJS 样板与 Mongoose 一起使用
为了舒适的开发(MongoDB + Mongoose),您必须克隆存储库,转到文件夹 my-app/
并复制 env-example-document
作为 .env
。
cd my-app/
cp env-example-document .env
改变 DATABASE_URL=mongodb://mongo:27017
到 DATABASE_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 样板,所以不会影响他们的互动。
无论您选择使用哪种数据库 – PostgreSQL 或 MongoDB(它们都很棒),选择应该取决于整个项目,在我们的样板中您可以选择)因此,如果您发现它有用但不喜欢,欢迎尝试它忘记点击图书馆的星星⭐。