Пошук уроків, статей та іншого контенту
Підключите Redis як транспорт і реалізуєте швидкий обмін повідомленнями між сервісами.
Redis-транспорт дає змогу обмінюватися повідомленнями між NestJS-сервісами через Redis. Один сервіс публікує повідомлення, а інший приймає його й обробляє.
Такий підхід корисний для:
взаємодії між мікросервісами;
виконання операцій без прямого HTTP-запиту;
обміну подіями між незалежними частинами системи;
швидкого передавання невеликих повідомлень.
У NestJS Redis використовується як транспорт для мікросервісів. Для роботи потрібні:
Redis-сервер;
пакет @nestjs/microservices;
пакет ioredis;
одна або більше NestJS-програм.
Для локального запуску Redis можна використати Docker:
docker run --name nest-redis -p 6379:6379 -d redisПеревірити запущені контейнери:
docker psЗа замовчуванням Redis буде доступний за адресою localhost:6379.
У NestJS-проєкті встановіть залежності:
npm install @nestjs/microservices ioredisРозглянемо приклад із двома програмами:
math-service — мікросервіс, який обробляє математичні повідомлення;
gateway — HTTP-застосунок, який надсилає повідомлення до math-service.
Спочатку створимо контролер мікросервісу.
// math-service/src/math.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
sum(numbers: number[]): number {
return numbers.reduce((total, number) => total + number, 0);
}
}Декоратор @MessagePattern() визначає шаблон повідомлення. Метод sum() буде викликано лише для повідомлень із шаблоном:
{ cmd: 'sum' }// math-service/src/main.ts
import { NestFactory } from '@nestjs/core';
import {
MicroserviceOptions,
Transport,
} from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap(): Promise<void> {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.REDIS,
options: {
host: 'localhost',
port: 6379,
},
},
);
await app.listen();
}
bootstrap();Тут:
Transport.REDIS вказує NestJS використовувати Redis;
host — адреса Redis-сервера;
port — порт Redis-сервера;
createMicroservice() створює застосунок, який слухає повідомлення, а не HTTP-запити.
Модуль мікросервісу:
// math-service/src/app.module.ts
import { Module } from '@nestjs/common';
import { MathController } from './math.controller';
@Module({
controllers: [MathController],
})
export class AppModule {}Щоб інший NestJS-застосунок міг надсилати повідомлення, у його модулі потрібно зареєструвати клієнт.
// gateway/src/app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { AppController } from './app.controller';
@Module({
imports: [
ClientsModule.register([
{
name: 'MATH_SERVICE',
transport: Transport.REDIS,
options: {
host: 'localhost',
port: 6379,
},
},
]),
],
controllers: [AppController],
})
export class AppModule {}Властивість name — це токен, за яким клієнт буде доступний через dependency injection.
Значення MATH_SERVICE має збігатися з токеном у @Inject().
HTTP-контролер може прийняти масив чисел і передати його мікросервісу.
// gateway/src/app.controller.ts
import {
Body,
Controller,
Inject,
Post,
} from '@nestjs/common';
import {
ClientProxy,
} from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';
@Controller('math')
export class AppController {
constructor(
@Inject('MATH_SERVICE')
private readonly mathClient: ClientProxy,
) {}
@Post('sum')
async sum(@Body() numbers: number[]): Promise<number> {
return firstValueFrom(
this.mathClient.send<number, number[]>(
{ cmd: 'sum' },
numbers,
),
);
}
}Метод send() використовується для повідомлень, які очікують відповідь.
Його аргументи:
шаблон повідомлення;
корисне навантаження.
У прикладі надсилається:
{
pattern: { cmd: 'sum' },
data: [2, 5, 10],
}Метод send() повертає Observable. Функція firstValueFrom() перетворює його на Promise, який зручно використовувати в async-методі контролера.
HTTP-застосунок запускається як звичайний NestJS-сервер:
// gateway/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();Після запуску обох застосунків можна надіслати запит:
curl -X POST http://localhost:3000/math/sum \
-H "Content-Type: application/json" \
-d '[2, 5, 10]'Відповідь:
17Послідовність роботи така:
HTTP-застосунок отримує запит.
ClientProxy публікує повідомлення в Redis.
math-service знаходить обробник для шаблону { cmd: 'sum' }.
Обробник обчислює суму.
Результат повертається через Redis до HTTP-застосунку.
HTTP-застосунок повертає результат клієнту.
У Redis-транспорті є два основні способи надсилання повідомлень.
Для запитів використовується send():
const result$ = this.mathClient.send(
{ cmd: 'sum' },
[2, 5, 10],
);На стороні мікросервісу використовується @MessagePattern():
@MessagePattern({ cmd: 'sum' })
sum(numbers: number[]): number {
return numbers.reduce((total, number) => total + number, 0);
}Такий варіант підходить, коли відправник не може продовжити роботу без результату операції.
Для подій використовується emit():
this.mathClient.emit('report_created', {
reportId: 42,
});На стороні мікросервісу подію обробляє @EventPattern():
import { Controller, Logger } from '@nestjs/common';
import { EventPattern } from '@nestjs/microservices';
@Controller()
export class ReportsController {
private readonly logger = new Logger(ReportsController.name);
@EventPattern('report_created')
handleReportCreated(payload: { reportId: number }): void {
this.logger.log(`Створено звіт: ${payload.reportId}`);
}
}emit() не призначений для отримання результату. Він використовується для повідомлень на кшталт:
користувача створено;
замовлення оплачено;
звіт сформовано;
потрібно запустити фонову операцію.
Адресу Redis не варто жорстко прописувати в коді. Для різних середовищ вона може відрізнятися.
Наприклад:
import { Transport } from '@nestjs/microservices';
const redisHost = process.env.REDIS_HOST ?? 'localhost';
const redisPort = Number(process.env.REDIS_PORT ?? 6379);
const redisOptions = {
host: redisHost,
port: redisPort,
};
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.REDIS,
options: redisOptions,
});Для Docker Compose або Kubernetes значення REDIS_HOST зазвичай відповідає імені сервісу Redis, а не localhost.
Важливо, що localhost усередині контейнера означає поточний контейнер, а не машину розробника чи інший контейнер.
Типи корисного навантаження можна виносити в окремі інтерфейси.
// shared/math.types.ts
export interface SumRequest {
numbers: number[];
}Мікросервіс використовує цей тип у контролері:
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { SumRequest } from './shared/math.types';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
sum(payload: SumRequest): number {
return payload.numbers.reduce(
(total, number) => total + number,
0,
);
}
}Клієнт надсилає об’єкт такого самого формату:
return firstValueFrom(
this.mathClient.send<number, SumRequest>(
{ cmd: 'sum' },
{ numbers: [2, 5, 10] },
),
);Спільні типи зменшують ризик помилок у структурі повідомлень. Водночас TypeScript-типи існують лише під час компіляції, тому за потреби валідації вхідних даних її потрібно виконувати окремо.
Якщо обробник повідомлення викидає помилку, клієнт, який використовує send(), може отримати помилку через Observable.
import { BadRequestException, Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
sum(numbers: number[]): number {
if (!Array.isArray(numbers)) {
throw new BadRequestException(
'Очікується масив чисел',
);
}
return numbers.reduce((total, number) => total + number, 0);
}
}На стороні клієнта помилку можна перехопити стандартним try...catch:
try {
return await firstValueFrom(
this.mathClient.send<number, number[]>(
{ cmd: 'sum' },
[2, 5, 10],
),
);
} catch (error) {
throw new Error('Мікросервіс не зміг обробити запит');
}Для production-застосунків варто також враховувати:
недоступність Redis;
зупинку мікросервісу;
перевищення часу очікування відповіді;
повторні спроби надсилання;
ідемпотентність обробників подій.
Клієнт і мікросервіс повинні підключатися до одного Redis-сервера:
{
host: 'localhost',
port: 6379,
}Якщо один застосунок використовує localhost, а інший — іншу адресу або порт, вони не зможуть обмінюватися повідомленнями.
Практично конфігурацію Redis часто виносять у змінні середовища:
REDIS_HOST=localhost
REDIS_PORT=6379І використовують однакові значення в обох застосунках.
Якщо Redis не працює на вказаному порту, клієнт або мікросервіс не зможе встановити з’єднання.
Перевірте:
docker psА також переконайтеся, що порт 6379 доступний.
Ці шаблони є різними:
{ cmd: 'sum' }{ command: 'sum' }Обробник повинен мати точно такий самий шаблон, який надсилає клієнт:
@MessagePattern({ cmd: 'sum' })emit() замість send()Якщо клієнт очікує результат, потрібно використовувати send():
this.mathClient.send({ cmd: 'sum' }, [1, 2, 3]);emit() призначений для подій без очікування відповіді:
this.mathClient.emit('numbers_received', [1, 2, 3]);firstValueFrom()send() повертає Observable, а не готове значення:
const result = this.mathClient.send(
{ cmd: 'sum' },
[1, 2, 3],
);Для отримання результату в async-методі використовуйте:
const result = await firstValueFrom(
this.mathClient.send({ cmd: 'sum' }, [1, 2, 3]),
);Якщо NestJS працює в контейнері, localhost посилається на цей самий контейнер. Для підключення до Redis в іншому контейнері потрібно використовувати ім’я Redis-сервісу в Docker-мережі.
Redis можна використовувати в NestJS як транспорт для мікросервісів.
Мікросервіс підключається через Transport.REDIS.
Обробники запитів оголошуються через @MessagePattern().
Обробники подій оголошуються через @EventPattern().
ClientProxy.send() використовується для запитів із відповіддю.
ClientProxy.emit() використовується для односторонніх подій.
Клієнт і мікросервіс повинні використовувати однакові налаштування Redis.
Для локальної розробки Redis можна запустити в Docker на порту 6379.