У світі архітектури програмного забезпечення, особливо в складних бізнес-додатках, підтримка чистого розділення команд і запитів може значно покращити масштабованість та підтримуваність вашої системи. І ось тут вступає в гру патерн Command Query Responsibility Segregation (CQRS). У цій статті ми розглянемо, як реалізувати патерн CQRS у додатку NestJS за допомогою TypeScript.
Що таке CQRS?
Патерн CQRS — це архітектурний патерн, який розділяє моделі для читання та запису даних. Простими словами, йдеться про те, щоб спроектувати додаток таким чином, що команди (які змінюють стан даних) і запити (які отримують дані) обробляються незалежно. Це розділення дозволяє оптимізувати продуктивність, безпеку, масштабованість і спрощує проектування складних додатків.
Чому NestJS?
NestJS — це прогресивний фреймворк для Node.js, який надає підтримку TypeScript з коробки і реалізує модульну архітектуру, що ідеально підходить для таких патернів, як CQRS. Завдяки своїй потужній системі Command та Query Bus, NestJS є ідеальним вибором для реалізації CQRS.
Налаштування проекту NestJS
Для початку вам потрібно створити базовий проект NestJS. Якщо ви ще не налаштували його, давайте створимо новий проект:
npm i -g @nestjs/cli
nest new my-cqrs-app
cd my-cqrs-app
Встановлення необхідних пакетів
NestJS надає спеціальний пакет для підтримки CQRS:
npm install @nestjs/cqrs
Реалізація CQRS у NestJS
Розглянемо патерн CQRS у NestJS на прикладі, де ми управляємо простим списком завдань. Ми створимо обробники як для команд, так і для запитів для завдань.
1. Визначення моделей команд і запитів
Спочатку потрібно визначити наші команди та запити. Це прості класи, які будуть служити об'єктами передачі даних (DTO).
// src/tasks/commands/impl/create-task.command.ts
export class CreateTaskCommand {
constructor(
public readonly title: string,
public readonly description: string,
) {}
}
// src/tasks/queries/impl/get-tasks.query.ts
export class GetTasksQuery {}
2. Створення обробників команд
Обробники команд відповідають за виконання логіки, пов'язаної з командою. Давайте створимо обробник для CreateTaskCommand
.
// src/tasks/commands/handlers/create-task.handler.ts
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { CreateTaskCommand } from '../impl/create-task.command';
@CommandHandler(CreateTaskCommand)
export class CreateTaskHandler implements ICommandHandler {
async execute(command: CreateTaskCommand) {
const { title, description } = command;
// Логіка створення завдання
console.log(`Creating task: ${title}`);
// Збереження завдання в базу даних (псевдо-постійне збереження для прикладу)
return { title, description };
}
}
3. Створення обробників запитів
Обробники запитів отримують дані з вашої системи. Давайте реалізуємо обробник для GetTasksQuery
.
// src/tasks/queries/handlers/get-tasks.handler.ts
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { GetTasksQuery } from '../impl/get-tasks.query';
@QueryHandler(GetTasksQuery)
export class GetTasksHandler implements IQueryHandler {
async execute(query: GetTasksQuery) {
// Логіка отримання завдань
console.log('Retrieving tasks');
// Отримання завдань з бази даних (псевдо-дані для прикладу)
return [{ title: 'Sample Task', description: 'This is a sample task' }];
}
}
Реєстрація обробників та реалізація модуля
Переконайтесь, що ваші обробники правильно зареєстровані в модулі NestJS.
// src/tasks/tasks.module.ts
import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { CreateTaskHandler } from './commands/handlers/create-task.handler';
import { GetTasksHandler } from './queries/handlers/get-tasks.handler';
@Module({
imports: [CqrsModule],
providers: [CreateTaskHandler, GetTasksHandler],
})
export class TasksModule {}
```
5. Надсилання команд і запитів
Нарешті, інжектуємо CommandBus
та QueryBus
, щоб відправляти команди та запити з контролерів або сервісів.
// src/tasks/tasks.service.ts
import { Injectable } from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { CreateTaskCommand } from './commands/impl/create-task.command';
import { GetTasksQuery } from './queries/impl/get-tasks.query';
@Injectable()
export class TasksService {
constructor(
private readonly commandBus: CommandBus,
private readonly queryBus: QueryBus,
) {}
async createTask(title: string, description: string) {
return this.commandBus.execute(new CreateTaskCommand(title, description));
}
async getTasks() {
return this.queryBus.execute(new GetTasksQuery());
}
}
Висновок
Реалізація патерну CQRS за допомогою NestJS надає дисциплінований підхід до розділення обов'язків у вашому додатку. Хоча ця стаття надає базове розуміння, важливо врахувати наслідки відкладеної узгодженості в розподілених системах, які часто супроводжують реалізацію CQRS.
Для подальшого вивчення вам може бути цікаво інтегрувати Event Sourcing з CQRS, оскільки ці патерни часто доповнюють один одного, особливо в складних і високо масштабованих системах.
Тепер, коли ви маєте основи, починайте створювати чисті, надійні та високо масштабовані додатки, використовуючи CQRS з NestJS! Бажаю успіху в кодуванні!
Перекладено з: Implementing the CQRS Pattern in a NestJS Application