1705824758
2024-01-18 12:06:22
私たちが作成した NestJS ボイラープレート 2020 年 8 月にそれ以来、私たちはその最適化と改善に取り組んできました。 NestJS ボイラープレートは、古典的な REST API アプローチを使用してプロジェクトを迅速に開始するために必要なすべてのライブラリと認証、メール送信などのソリューションを含むプロジェクトです。 現在、この定型文には Github 上で 1.8,000 個のスターが付いており、開発者コミュニティからの認知とサポートを得ています。 最近、新作も出版しました React のフロントエンド定型文 バックエンド実装との互換性も抜群なので、ぜひチェックしてみてください。
Mongo サポートを含める動機
PostgreSQL のサポートは、その信頼性、データの整合性、および活発なコミュニティのため、当初定型文に含まれていました。 ただし、大規模なデータ セットを高速に処理し、高いスケーラビリティを必要とするプロジェクトの場合は、通常、MongoDB の方が良い選択です。 そこで、MongoDB サポートをプロジェクトに統合したいと考えました。 また、この定型文を使用するコミュニティ メンバーや同僚から、NoSQL DB サポートを含めてほしいという多くのリクエストがありました。
これで、開発者はドキュメント指向データベース MongoDB とリレーショナル データベース PostgreSQL のどちらかを選択できるようになりました。
ここで、新しいプロジェクトをセットアップするときに何を使用するのが良いかを考えてみましょう。 もちろん、問題はどちらのデータベースが優れているかということではありません。どちらのデータベースも優れているためです。すべてはアプリケーションの範囲と目標によって異なります。 詳細を見ていきましょう。
- 複雑な SQL リクエストを使用し、リレーショナル テーブル構造をサポートするほとんどのアプリで動作するリレーショナル データベースが必要な場合は、PostgreSQL を選択することをお勧めします。
- 高レベルのセキュリティと高度な ACID 準拠が必要なシナリオの場合、PostgreSQL が最適なソリューションです。
- 複数構造で急速に変化するデータを扱うアプリケーションで複雑なトランザクションや分析を処理するための信頼できるツールが必要な場合、MongoDB はプロジェクトに適した選択肢です。
- データの局所性やデータ主権のためにスケールする必要があり、複数のリージョンに分散する必要があるアプリケーションを実行している場合、MongoDB のスケールアウト アーキテクチャはそれらのニーズを自動的に満たします。
MongoDB と PostgreSQL の比較について詳しく知りたい場合は、次の記事を参照することをお勧めします。 この記事。
適切なレベルの抽象化を提供し、MongoDB での作業を簡素化するために、 マングース – オブジェクト データ モデリング (ODM) ライブラリ。 これにより、開発者はスキーマベースのアプローチを使用してデータ モデルを定義でき、MongoDB の操作プロセスを簡素化する豊富な機能セットが提供されます。 Mongoose は、基本的な CRUD 操作とクエリ関数をそのままサポートするだけでなく、ミドルウェア関数、仮想プロパティ、クエリ ビルダー、スキーマ検証など、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 TypeORM を操作するために UserRepository を実装します。
@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 スキーマ
スキーマは、最高のパフォーマンスとスケーラビリティを実現する MongoDB のベスト プラクティスに基づいて構築されました。 NoSQL データベースのスキーマ設計は、リレーショナル データベースのスキーマ設計と同じではありません。 違いの 1 つは、リレーショナルでは、重複などを避けるためにスキーマを通常の形式に減らすオプションがあることです。一方、NoSQL では、データを複製して「結合」を回避できます。これにより、最高のパフォーマンス インジケーターが達成されます。データサンプリング。 違いを確認するために、例として定型文を見てみましょう。 PostgreSQL のデータベース スキーマは次のようになります。
データテーブルの例:
MongoDB について言えば、ここで PostgreSQL から設計エクスペリエンスを転送することはできません。つまり、4 つのコレクション ユーザー、ファイル (写真用)、ロール、ステータスを作成し、他のコレクションへのリンクをユーザー コレクションに保存し、集計を使用してデータ サンプリング中に保存することはできません ( $lookup) パフォーマンスに影響するため、追加のデータを追加します (結合の比較について詳しく読む) 記事上で、2020年というと古いように思えますが、それでも実際にあります)。 スキームはどのようなものであるべきでしょうか? すべては非常に単純です。すべてのデータは 1 つのコレクションに保存する必要があります。
そして、ユーザーを取得するときに、ユーザーの写真、役割、ステータスに関するデータを取得するために追加のリクエストを行う必要がなくなります。実際、すべてのデータはユーザーのコレクションにすでに保存されているため、生産性が向上します。
MongoDB スキーマの設計について詳しく知りたい場合は、以下を参照することをお勧めします。 この記事。
Mongoose で NestJS ボイラープレートを使用する方法
快適な開発 (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 (どちらも優れています) のどのデータベースを使用するかに関係なく、選択はプロジェクト全体に依存する必要があり、定型文ではその選択が可能です)。図書館⭐の星をクリックするのを忘れてください。
この記事の完全なクレジットは次のとおりです ヴラド・シチェポティン そして エレナ・ヴラセンコ 🇺🇦
#ヘキサゴナル #アーキテクチャによる #NestJS #ボイラープレートの #MongoDB #サポート




