Мікросервіси на NestJS у 2026: архітектура, gRPC та питання співбесід

Практичний посібник з архітектури мікросервісів NestJS на gRPC: межі сервісів, транспортні рівні, патерни стрімінгу та поширені питання співбесід на 2026 рік.

Архітектура мікросервісів NestJS з комунікацією gRPC між розподіленими бекенд-сервісами

Мікросервіси NestJS стали стандартним підходом до побудови розподілених бекендів на Node.js у 2026 році. Завдяки покращеному рівню транспортерів у NestJS 11, першокласній підтримці gRPC та автоматичному поширенню trace ID фреймворк надає все необхідне для розбиття моноліту на чітко відмежовані сервіси без втрати зручності для розробника.

gRPC проти REST для внутрішніх сервісів

gRPC використовує Protocol Buffers і HTTP/2, забезпечуючи до 10 разів швидшу серіалізацію та нативний двонаправлений стрімінг порівняно з JSON через REST. Для взаємодії сервіс-сервіс усередині кластера gRPC зменшує затримку та забезпечує суворі контракти через файли .proto. REST залишається кращим вибором для публічних API, які споживають браузери та сторонні системи.

Архітектура транспортного рівня мікросервісів NestJS

Пакет @nestjs/microservices замінює HTTP-контролери обробниками, орієнтованими на повідомлення. Замість маршрутизації за URL-шляхом кожен обробник реагує на патерн повідомлення (message pattern) — рядок або ключ обʼєкта, що ідентифікує операцію. Ця абстракція означає, що та сама логіка обробника працює через TCP, Redis, NATS, Kafka, RabbitMQ чи gRPC без змін у коді.

NestJS 11 запровадив метод unwrap() на всіх транспортерах, що надає прямий доступ до базового екземпляра клієнта. Це вирішує давню проблему: інспекцію стану зʼєднання, налаштування інтервалів keepalive чи доступ до специфічних для брокера можливостей, які абстракція NestJS не розкриває.

Два патерни комунікації керують мікросервісами NestJS:

  • Запит-відповідь (@MessagePattern): клієнт надсилає повідомлення й чекає на відповідь. Підходить для синхронних запитів на кшталт отримання профілю користувача чи перевірки токена.
  • На основі подій (@EventPattern): клієнт випромінює подію й рухається далі — відповіді не очікується. Ідеально для журналів аудиту, сповіщень чи запуску процесів на стороні споживача.
orders.controller.tstypescript
import { Controller } from '@nestjs/common';
import { MessagePattern, EventPattern, Payload } from '@nestjs/microservices';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';

@Controller()
export class OrdersController {
  constructor(private readonly ordersService: OrdersService) {}

  // Request-response: caller waits for the created order
  @MessagePattern('order.create')
  async createOrder(@Payload() data: CreateOrderDto) {
    return this.ordersService.create(data);
  }

  // Event-based: fire and forget, no response returned
  @EventPattern('order.shipped')
  async handleOrderShipped(@Payload() data: { orderId: string }) {
    await this.ordersService.markAsShipped(data.orderId);
  }
}

Обробник @MessagePattern автоматично серіалізує повернуте значення й надсилає його назад через транспорт. Обробник @EventPattern нічого не повертає — NestJS відкидає будь-яке повернуте значення.

Налаштування gRPC як транспорту мікросервісу NestJS

Інтеграція gRPC у NestJS починається з файлу .proto, який визначає контракт сервісу. Protocol Buffers забезпечують безпеку типів на рівні мережі — і клієнт, і сервер мають узгодити точну форму кожного повідомлення, перш ніж бодай один байт пройде мережею.

proto/users.protoprotobuf
syntax = "proto3";

package users;

service UsersService {
  rpc FindOne (UserById) returns (User);
  rpc FindMany (UserFilter) returns (stream User);
}

message UserById {
  string id = 1;
}

message UserFilter {
  string role = 1;
  int32 limit = 2;
}

message User {
  string id = 1;
  string email = 2;
  string name = 3;
  string role = 4;
}

На стороні сервера застосунок NestJS завантажує мікросервіс gRPC, вказуючи на файл proto та привʼязуючи мережеву адресу:

main.tstypescript
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.GRPC,
      options: {
        package: 'users',
        protoPath: join(__dirname, 'proto/users.proto'),
        url: '0.0.0.0:5000',
      },
    },
  );
  await app.listen();
}
bootstrap();

Контролер використовує @GrpcMethod замість @MessagePattern. Декоратор приймає назву сервісу та назву методу RPC як аргументи, відображаючи їх безпосередньо на визначення proto:

users.controller.tstypescript
import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';
import { UsersService } from './users.service';

@Controller()
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @GrpcMethod('UsersService', 'FindOne')
  async findOne(data: { id: string }) {
    return this.usersService.findById(data.id);
  }
}

NestJS автоматично обробляє завантаження proto, серіалізацію та керування зʼєднанням HTTP/2. Метод контролера отримує звичайні обʼєкти TypeScript — ручне декодування proto не потрібне.

Патерни стрімінгу gRPC у NestJS

Серверний стрімінг підходить для сценаріїв, де один запит породжує кілька результатів з часом — як-от стрічки цін у реальному часі, посторінкові курсори бази даних чи оновлення прогресу під час тривалого завдання. Обробник повертає Observable з RxJS, а NestJS стрімить кожне випромінене значення клієнту як окреме повідомлення gRPC:

users.controller.ts — server streamingtypescript
import { Observable, from } from 'rxjs';
import { map } from 'rxjs/operators';
import { GrpcMethod } from '@nestjs/microservices';

@GrpcMethod('UsersService', 'FindMany')
findMany(data: { role: string; limit: number }): Observable<any> {
  // Stream users matching the filter one by one
  const users$ = from(this.usersService.findByRole(data.role, data.limit));
  return users$.pipe(
    map((user) => ({
      id: user.id,
      email: user.email,
      name: user.name,
      role: user.role,
    })),
  );
}

Двонаправлений стрімінг використовує @GrpcStreamCall(). І клієнт, і сервер надсилають незалежні потоки — корисно для чат-систем, спільного редагування чи мультиплексованого збору телеметрії. Обробник отримує екземпляр grpc.ServerDuplexStream і керує циклом читання/запису вручну.

Обробка помилок у стрімінгу

Observable, який аварійно завершився всередині потоку gRPC, припиняє весь виклик зі статусом CANCELLED. Логіку потоку слід обгортати операторами catchError і випромінювати відповідні для gRPC метадані помилки. Необроблені помилки потоку — причина номер один загадкових розривів зʼєднання у продакшн-сервісах NestJS gRPC.

Гібридні застосунки: HTTP і gRPC в одному сервісі

Багато реальних сервісів NestJS потребують як публічного REST API, так і внутрішніх ендпойнтів gRPC. Патерн гібридного застосунку підʼєднує кілька транспортів до одного екземпляра NestJS:

main.ts — hybrid applicationtypescript
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
  // HTTP server on port 3000
  const app = await NestFactory.create(AppModule);

  // gRPC microservice on port 5000
  app.connectMicroservice<MicroserviceOptions>({
    transport: Transport.GRPC,
    options: {
      package: 'users',
      protoPath: join(__dirname, 'proto/users.proto'),
      url: '0.0.0.0:5000',
    },
  });

  await app.startAllMicroservices();
  await app.listen(3000);
}
bootstrap();

Цей підхід дозволяє уникнути розгортання окремих сервісів, коли один відмежований контекст потребує і зовнішнього, і внутрішнього інтерфейсів. Той самий контейнер впровадження залежностей, ґарди, перехоплювачі та пайпи застосовуються до обох транспортів — це зменшує дублювання й зберігає узгодженість поведінки.

Готовий до співбесід з Node.js / NestJS?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Межі сервісів і Domain-Driven Design у NestJS

Розбиття моноліту на мікросервіси без чітких меж створює розподілений моноліт — найгірше з обох архітектур. Кожен мікросервіс NestJS має володіти єдиним відмежованим контекстом: власною базою даних, власними доменними моделями та власним життєвим циклом розгортання.

Практичні рекомендації щодо проведення меж сервісів у NestJS:

  • Один модуль NestJS на корінь агрегату. Якщо Order і OrderLine завжди змінюються разом, вони належать до одного сервісу. Якщо User і Order змінюються незалежно, вони заслуговують на окремі сервіси.
  • Асинхронні події для побічних ефектів між доменами. Коли оформлено замовлення, слід випромінити подію order.created через Kafka чи RabbitMQ. Сервіс складу реагує, а сервіс замовлень не знає про його існування.
  • Спільні proto-пакети для контрактів. Файли .proto слід зберігати у спільному репозиторії. І виробник, і споживач генерують типи з одного джерела істини — розходження стає помилкою CI, а не інцидентом у продакшені.

Опції мікросервісів із контейнера DI у NestJS 11 роблять конфігурацію сервісу тестованою. Замість жорсткого прописування URL брокера в main.ts можна впровадити ConfigService і змінювати рядки зʼєднання для кожного середовища без перезбирання.

Патерни надійності: тайм-аути, повторні спроби та запобіжники

Внутрішні виклики gRPC без дедлайнів перетворюють одну повільну залежність на загальносистемний збій. Кожен виклик ClientProxy.send() має містити тайм-аут:

orders.service.tstypescript
import { Inject, Injectable } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { firstValueFrom, timeout, retry } from 'rxjs';

@Injectable()
export class OrdersService {
  private usersService: any;

  constructor(@Inject('USERS_PACKAGE') private client: ClientGrpc) {}

  onModuleInit() {
    this.usersService = this.client.getService('UsersService');
  }

  async getOrderWithUser(orderId: string, userId: string) {
    // 3-second deadline, 2 retries with exponential backoff
    const user = await firstValueFrom(
      this.usersService.findOne({ id: userId }).pipe(
        timeout(3000),
        retry({ count: 2, delay: (err, retryCount) => {
          const jitter = Math.random() * 100;
          return new Promise(r => setTimeout(r, 1000 * retryCount + jitter));
        }}),
      ),
    );
    return { orderId, user };
  }
}

Повторні спроби слід застосовувати лише до ідемпотентних операцій. Повторення виклику CreateOrder ризикує створити дублікати замовлень. Повторення запиту FindOne безпечне, бо він не має побічних ефектів.

Для функціональності запобіжника (circuit breaker) бібліотека opossum добре інтегрується з NestJS. Виклики ClientGrpc слід обгорнути в запобіжник, який відкривається після N послідовних збоїв і закривається після конфігурованого тайм-ауту скидання.

Поширення trace ID у NestJS 11

NestJS 11 автоматично поширює trace ID через транспорти Kafka, RabbitMQ та gRPC. У поєднанні з OpenTelemetry це забезпечує повне розподілене трасування без власних перехоплювачів — значне покращення порівняно з NestJS 10, де було потрібне ручне поширення контексту.

Питання співбесід щодо мікросервісів NestJS

Технічні співбесіди на бекенд-позиції дедалі частіше перевіряють знання мікросервісів поряд зі специфічними для фреймворка патернами. Наведені питання охоплюють концепції, які найчастіше ставлять на співбесідах, орієнтованих на NestJS.

Яка різниця між @MessagePattern і @EventPattern?

@MessagePattern реалізує запит-відповідь: клієнт надсилає повідомлення й блокується, доки не отримає відповідь. @EventPattern реалізує модель fire-and-forget: клієнт випромінює подію й одразу продовжує виконання. Під капотом @MessagePattern повертає результат обробника через транспорт, тоді як @EventPattern відкидає повернуті значення.

Як NestJS завантажує та привʼязує файли .proto для gRPC?

NestJS використовує пакет @grpc/proto-loader для розбору файлів .proto під час запуску та генерації дескрипторів сервісів. Конфігурація GrpcOptions задає шлях до proto та назву пакета. Контролери, позначені @GrpcMethod('ServiceName', 'MethodName'), зіставляються з розібраними дескрипторами. Якщо метод, оголошений у proto, не має відповідного обробника, NestJS викидає помилку під час bootstrap.

Коли gRPC має замінити REST між мікросервісами?

gRPC підходить для внутрішніх викликів сервіс-сервіс, де важливі безпека типів, продуктивність і стрімінг. Protocol Buffers створюють менші корисні навантаження та швидшу серіалізацію, ніж JSON. Мультиплексування HTTP/2 зменшує накладні витрати на зʼєднання. REST залишається кращим для публічних API, браузерних клієнтів і сторонніх інтеграцій, де читабельність для людини та широка підтримка інструментів переважають над сирою продуктивністю.

Як працює патерн гібридного застосунку?

NestFactory.create() запускає HTTP-сервер. Виклик app.connectMicroservice() приєднує додаткові транспорти — gRPC, Kafka, Redis чи будь-який підтримуваний транспортер — до того самого екземпляра застосунку NestJS. Усі транспорти спільно використовують одне дерево модулів, контейнер DI та конвеєр middleware. app.startAllMicroservices() запускає всі підʼєднані транспортери, а app.listen() запускає рівень HTTP.

Які патерни надійності запобігають каскадним збоям?

Найважливіші три патерни: тайм-аути (кожен виклик RPC потребує дедлайну), повторні спроби з джитером (лише для ідемпотентних операцій, з експоненційним відкатом плюс випадковим джитером для уникнення ефекту натовпу) та запобіжники (відкриті після N збоїв, напіввідкриті після періоду охолодження, закриті після успішних перевірок працездатності). Інтеграція NestJS з RxJS робить тайм-аути та повторні спроби компонованими через оператори pipe.

Для глибшої практики з патернами архітектури NestJS модуль модулі та впровадження залежностей у NestJS розглядає основи DI, що лежать в основі композиції мікросервісів. Модуль middleware та перехоплювачі розглядає наскрізні питання, спільні для транспортів.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Висновок

  • Транспортна абстракція NestJS 11 дозволяє сервісам перемикатися між TCP, gRPC, Kafka та NATS без переписування логіки обробників — декоратори @MessagePattern і @EventPattern працюють однаково в усіх транспортах
  • gRPC з Protocol Buffers забезпечує суворі контракти між сервісами через файли .proto, виявляючи помилки інтеграції на етапі компіляції, а не у продакшені
  • Гібридні застосунки поєднують HTTP і gRPC в одному екземплярі NestJS, усуваючи потребу розгортати окремі сервіси для публічних і внутрішніх інтерфейсів
  • Серверний стрімінг через Observable з RxJS уможливлює потоки даних у реальному часі без складності WebSocket — NestJS відображає кожне випромінення Observable на повідомлення потоку gRPC
  • Кожен внутрішній виклик RPC потребує дедлайну, повторні спроби застосовуються лише до ідемпотентних операцій, а запобіжники не дають одному повільному сервісу каскадно спричинити повний збій
  • Межі сервісів випливають із Domain-Driven Design: один відмежований контекст на сервіс, асинхронні події для комунікації між доменами, спільні proto-пакети для забезпечення контрактів

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Теги

#nestjs
#microservices
#grpc
#nodejs
#typescript
#architecture

Поділитися

Пов'язані статті