Микросервисы с CQRS и Event Sourcing — это архитектура, которая в последние годы приобрела большую популярность. В этой статье мы рассмотрим, как создавать микросервисы с CQRS и Event Sourcing в TypeScript с использованием NestJS.

Прежде чем мы начнем, давайте разберемся, что такое CQRS и Event Sourcing:

CQRS (Command Query Responsibility Segregation) — это архитектурный шаблон, который отделяет операции записи (команды) от операций чтения (запросов). Это разделение позволяет по-разному оптимизировать операции чтения и записи, что приводит к повышению производительности и масштабируемости.

Источник событий — это способ хранения и извлечения данных путем сохранения всех изменений данных в виде последовательности событий. Вместо сохранения текущего состояния объекта мы сохраняем события, которые привели к текущему состоянию. Это позволяет нам восстановить состояние объекта в любой момент времени, воспроизводя события.

NestJS — это популярная платформа Node.js, которая обеспечивает структурированный и эффективный способ создания серверных приложений. Он построен на основе Express.js и предоставляет множество функций, таких как внедрение зависимостей, промежуточное программное обеспечение и многое другое.

Теперь давайте приступим к созданию микросервисов с помощью CQRS и Event Sourcing в TypeScript с использованием NestJS.

Создание микросервисов с безопасностью типов? Может быть хорошей идеей использовать набор инструментов с открытым исходным кодом, такой как Bit, для идентификации и изоляции компонентов в вашей кодовой базе, определяя их как независимые сущности, которые можно разрабатывать и тестировать по отдельности, а затем версионировать и документировать. , протестировано и опубликовано через центральный реестр. Подробнее здесь.

Шаг 1: Создайте новый проект NestJS Мы начнем с создания нового проекта NestJS с помощью интерфейса командной строки Nest. Откройте окно терминала и выполните следующую команду:

npm i -g @nestjs/cli
nest new project-name

Шаг 2. Установите необходимые пакеты. Чтобы создать микросервисы с CQRS и Event Sourcing в NestJS, нам нужно установить некоторые необходимые пакеты. Откройте окно терминала в каталоге проекта и выполните следующую команду:

npm install @nestjs/microservices @nestjs/cqrs @nestjs/event-sourcing

Шаг 3. Создание микрослужбы Чтобы создать микрослужбу в NestJS, нам нужно определить контроллер, который будет обрабатывать входящие запросы и ответы. Создайте новый файл с именем app.controller.ts в каталоге src и добавьте следующий код:

import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { CommandBus } from '@nestjs/cqrs';
import { CreateMessageCommand } from './commands/create-message.command';

@Controller()
export class AppController {
  constructor(private readonly commandBus: CommandBus) {}

  @MessagePattern({ cmd: 'create-message' })
  async createMessage(data: { text: string }) {
    return this.commandBus.execute(new CreateMessageCommand(data.text));
  }
}

В этом коде мы определили контроллер, который прослушивает сообщения с помощью команды create-message. Когда сообщение получено, вызывается функция createMessage, которая создает новый CreateMessageCommand и выполняет его с помощью функции CommandBus.

Шаг 4: Создайте команду Теперь нам нужно создать команду, которая будет выполняться CommandBus. Создайте новый файл с именем create-message.command.ts в каталоге src/commands и добавьте следующий код:

import { ICommand } from '@nestjs/cqrs';

export class CreateMessageCommand implements ICommand {
  constructor(public readonly text: string) {}
}

В этом коде мы определили CreateMessageCommand, реализующий интерфейс ICommand. Конструктор принимает параметр text, представляющий текст сообщения.

Шаг 5: Создайте событие. Когда команда выполняется, нам нужно создать событие, чтобы уведомить все заинтересованные стороны. Создайте новый файл с именем message-created.event.ts в каталоге src/events и добавьте следующий код:

import { IEvent } from '@nestjs/cq
import { IMessage } from '../interfaces/message.interface';

export class MessageCreatedEvent implements IEvent {
constructor(public readonly message: IMessage) {}
}

В этом коде мы определили MessageCreatedEvent, реализующий интерфейс IEvent. Конструктор принимает параметр message, который представляет созданное сообщение.

Шаг 6: Создайте хранилище событий Для хранения событий нам нужно создать хранилище событий. Создайте новый файл с именем `message.event-store.ts` в каталоге `src/event-stores` и добавьте следующий код:

import { Injectable } from '@nestjs/common';
import { IEvent } from '@nestjs/cqrs';
import { EventStore } from '@nestjs/event-sourcing';
import { MessageCreatedEvent } from '../events/message-created.event';

@Injectable()
export class MessageEventStore {
  @EventStore()
  events: IEvent[];

  handleMessageCreatedEvent(event: MessageCreatedEvent) {
    this.events.push(event);
  }

В этом коде мы определили хранилище событий, в котором события хранятся в виде массива. Мы также определили функцию с именем handleMessageCreatedEvent, которая вызывается, когда генерируется MessageCreatedEvent. Эта функция просто помещает событие в массив events.

Шаг 7: Создайте обработчик запросов Чтобы прочитать данные, нам нужно создать обработчик запросов. Создайте новый файл с именем message.query-handler.ts в каталоге src/query-handlers и добавьте следующий код:

import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { IMessage } from '../interfaces/message.interface';
import { GetMessagesQuery } from './queries/get-messages.query';
import { MessageEventStore } from '../event-stores/message.event-store';

@QueryHandler(GetMessagesQuery)
export class GetMessagesQueryHandler implements IQueryHandler<GetMessagesQuery> {
  constructor(private readonly messageEventStore: MessageEventStore) {}

  async execute(query: GetMessagesQuery): Promise<IMessage[]> {
    const events = this.messageEventStore.events.filter(event => event instanceof MessageCreatedEvent) as MessageCreatedEvent[];
    return events.map(event => event.message);
  }
}

В этом коде мы определили обработчик запросов, который обрабатывает GetMessagesQuery. Функция execute возвращает массив сообщений, фильтруя массив events для событий MessageCreatedEvent и сопоставляя их со свойством сообщения.

Шаг 8: Создайте запрос Чтобы получить данные, нам нужно определить запрос. Создайте новый файл с именем get-messages.query.ts в каталоге src/queries и добавьте следующий код:

import { IQuery } from '@nestjs/cqrs';

export class GetMessagesQuery implements IQuery {}

В этом коде мы определили GetMessagesQuery, реализующий интерфейс IQuery.

Шаг 9: Определите интерфейс Чтобы определить структуру данных сообщения, нам нужно определить интерфейс. Создайте новый файл с именем message.interface.ts в каталоге src/interfaces и добавьте следующий код:

export interface IMessage {
  id: number;
  text: string;
}

В этом коде мы определили интерфейс IMessage, который имеет свойства id и text.

Шаг 10: Обновите файл app.module.ts Наконец, нам нужно обновить файл app.module.ts, чтобы зарегистрировать необходимых провайдеров и контроллеров. Добавьте следующий код в массивы providers и controllers:

import { Module } from '@nestjs/common';
import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs';
import { MessageController } from './controllers/message.controller';
import { MessageCreatedEvent } from './events/message-created.event';
import { MessageEventStore } from './event-stores/message.event-store';
import { GetMessagesQueryHandler } from './query-handlers/get-messages.query-handler';
import { GetMessagesQuery } from './queries/get-messages.query';

@Module({
  imports: [CqrsModule],
  controllers: [MessageController],
  providers: [
    {
      provide: CommandBus,
      useValue: new CommandBus(),
    },
    {
      provide: QueryBus,
      useValue: new QueryBus(),
    },
    MessageEventStore,
    MessageCreatedEvent,
    GetMessagesQueryHandler,
    GetMessagesQuery,
  ],
})
export class AppModule {}

В этом коде мы импортировали Module, CommandBus, CqrsModule и QueryBus из @nestjs/common и @nestjs/cqrs. Мы также импортировали контроллеры, события, хранилище событий, обработчики запросов и запросы, которые создали ранее.

Мы определили класс AppModule и украсили его декоратором @Module. Мы импортировали CqrsModule и зарегистрировали его в массиве imports.

Мы зарегистрировали MessageController в массиве controllers.

Мы зарегистрировали CommandBus и QueryBus в качестве поставщиков со свойством useValue. Мы также зарегистрировали MessageEventStore, MessageCreatedEvent, GetMessagesQueryHandler и GetMessagesQuery в качестве поставщиков.

Используя свойства provide и useValue, мы указываем NestJS использовать экземпляры, которые мы создали для этих классов.

Обратите внимание, что мы не используем внедрение зависимостей для экземпляров CommandBus и QueryBus. Мы просто создаем новые экземпляры, используя ключевое слово new. Это потому, что у нас нет никаких зависимостей для этих классов.

Наконец, мы экспортируем класс AppModule.

Шаг 11. Протестируйте приложение
Чтобы протестировать приложение, выполните следующую команду:

npm run start:dev

Это запустит приложение NestJS в режиме разработки.

Чтобы создать новое сообщение, отправьте запрос POST на конечную точку `/messages` со следующей полезной нагрузкой:

{
 "text": "Hello, world!"
}

Чтобы получить сообщения, отправьте запрос GET на конечную точку /messages.

Спасибо, что прочитали! Если вам понравилось читать этот пост, вы получили от него помощь, знания, вдохновение и мотивацию и хотите поддержать меня — вы можете Купить мне кофе. Ваша поддержка действительно имеет значение.

Поздравляем! Вы успешно внедрили микросервисную архитектуру с CQRS и источниками событий с помощью TypeScript и NestJS.

Создавайте приложения с повторно используемыми компонентами, как Lego

Инструмент с открытым исходным кодом Bit помогает более чем 250 000 разработчиков создавать приложения с компонентами.

Превратите любой пользовательский интерфейс, функцию или страницу в компонент многократного использования — и поделитесь им со своими приложениями. Легче сотрудничать и строить быстрее.

Подробнее

Разделите приложения на компоненты, чтобы упростить разработку приложений, и наслаждайтесь наилучшими возможностями для рабочих процессов, которые вы хотите:

Микро-интерфейсы

Система дизайна

Совместное использование кода и повторное использование

Монорепо

Узнать больше: