Skip to main content
Практика завдань

Що таке патерн CQRS і як його реалізувати в NestJS?

CQRS у NestJS

CQRS (Розділення відповідальності за команди та запити) відокремлює операції читання (запити) від операцій запису (команди). NestJS надає пакет @nestjs/cqrs для реалізації цього патерну.


Чому CQRS?

Без CQRSЗ CQRS
Одна модель для читання та записуОкремі моделі
Одна база данихМожна використовувати різні БД
Сервіс робить всеЧітке розділення обов'язків
Важко масштабувати читання/записи незалежноМасштабування незалежно

Налаштування

bash
npm install @nestjs/cqrs
typescript
import { Module } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; @Module({ imports: [CqrsModule], // ... }) export class OrdersModule {}

Команди (Операції запису)

Визначити команду

typescript
export class CreateOrderCommand { constructor( public readonly userId: string, public readonly items: { productId: string; quantity: number }[], public readonly shippingAddress: string, ) {} }

Обробник команди

typescript
import { CommandHandler, ICommandHandler, EventBus } from '@nestjs/cqrs'; @CommandHandler(CreateOrderCommand) export class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> { constructor( private readonly orderRepository: OrderRepository, private readonly eventBus: EventBus, ) {} async execute(command: CreateOrderCommand) { const { userId, items, shippingAddress } = command; const order = await this.orderRepository.create({ userId, items, shippingAddress, status: 'pending', }); // Публікація доменної події this.eventBus.publish(new OrderCreatedEvent(order.id, userId)); return order; } }

Запити (Операції читання)

Визначити запит

typescript
export class GetOrderByIdQuery { constructor(public readonly orderId: string) {} } export class GetUserOrdersQuery { constructor( public readonly userId: string, public readonly page: number = 1, ) {} }

Обробник запиту

typescript
import { QueryHandler, IQueryHandler } from '@nestjs/cqrs'; @QueryHandler(GetOrderByIdQuery) export class GetOrderByIdHandler implements IQueryHandler<GetOrderByIdQuery> { constructor(private readonly orderReadRepository: OrderReadRepository) {} async execute(query: GetOrderByIdQuery) { return this.orderReadRepository.findById(query.orderId); } }

Події

Визначити подію

typescript
export class OrderCreatedEvent { constructor( public readonly orderId: string, public readonly userId: string, ) {} }

Обробник події

typescript
import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; @EventsHandler(OrderCreatedEvent) export class OrderCreatedHandler implements IEventHandler<OrderCreatedEvent> { constructor( private readonly emailService: EmailService, private readonly analyticsService: AnalyticsService, ) {} async handle(event: OrderCreatedEvent) { await this.emailService.sendOrderConfirmation(event.userId, event.orderId); await this.analyticsService.trackOrder(event.orderId); } }

Використання в контролері

typescript
import { CommandBus, QueryBus } from '@nestjs/cqrs'; @Controller('orders') export class OrdersController { constructor( private readonly commandBus: CommandBus, private readonly queryBus: QueryBus, ) {} @Post() async create(@Body() dto: CreateOrderDto, @CurrentUser() user: User) { return this.commandBus.execute( new CreateOrderCommand(user.id, dto.items, dto.shippingAddress), ); } @Get(':id') async findOne(@Param('id') id: string) { return this.queryBus.execute(new GetOrderByIdQuery(id)); } }

Реєстрація обробників у модулі

typescript
const CommandHandlers = [CreateOrderHandler, CancelOrderHandler]; const QueryHandlers = [GetOrderByIdHandler, GetUserOrdersHandler]; const EventHandlers = [OrderCreatedHandler, OrderCancelledHandler]; @Module({ imports: [CqrsModule], controllers: [OrdersController], providers: [ ...CommandHandlers, ...QueryHandlers, ...EventHandlers, OrderRepository, OrderReadRepository, ], }) export class OrdersModule {}

Коли використовувати CQRS

Використовуйте CQRS, колиНе використовуйте, коли
Складна бізнес-логікаПростий CRUD-додаток
Різні моделі читання/записуМаленькі проекти
Архітектура, орієнтована на подіїПрототип/МVP
Потрібен аудитКоманда не знайома з патерном
Незалежне масштабування читання/записуРизик надмірної інженерії

Порада: CQRS додає складність. Починайте з простих сервісів. Вводьте CQRS лише тоді, коли ваші патерни читання та запису суттєво розходяться, або коли вам потрібне подієве джерело.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань