Когда выбирать микросервисную архитектуру в NestJS?
Когда монолит уже не справляется
NestJS изначально спроектирован так, что один и тот же код можно запускать как монолитное HTTP-приложение и как набор микросервисов через @nestjs/microservices с транспортами TCP, Redis, NATS, Kafka, RabbitMQ, gRPC. Это значит, что переход к микросервисам — это не смена фреймворка, а архитектурное решение, которое должно быть обосновано.
Зрелый ответ начинается с того, что по умолчанию выбирается модульный монолит. NestJS даёт для этого все инструменты: модули, DI-контейнер, чёткие границы через imports/exports, динамические модули. Микросервисы стоит вводить тогда, когда монолит начинает мешать бизнесу, а не наоборот.
Технические триггеры для перехода
- Разные профили нагрузки. Один модуль (например, обработка видео или ML-инференс) требует GPU и тяжёлой памяти, другой — лёгкий CRUD. Масштабировать монолит под пик одного модуля дорого.
- Разные SLA и требования к отказоустойчивости. Платёжный модуль должен иметь 99.99%, а внутренний дашборд — 99%. Изоляция падений критична.
- Разные технологические стеки. Часть системы выгоднее писать на Go или Python (ML), и интеграция через брокер сообщений проще, чем втягивать чужой код в монолит.
- Необходимость независимого деплоя. Если релизный цикл одного модуля блокирует остальные — это сигнал.
- Eventual consistency допустима. Если бизнес-процесс терпит асинхронность, микросервисы и event-driven подход дают выигрыш.
Организационные триггеры (закон Конвея)
- Несколько автономных команд, каждая со своим доменом и ритмом релизов.
- Чёткие bounded contexts в терминах DDD — границы сервисов должны совпадать с границами доменов, иначе получится распределённый монолит.
- Команда уже умеет в observability, CI/CD, контейнеры и infrastructure-as-code. Без этого микросервисы превращаются в боль.
Когда микросервисы — это ошибка
- Маленькая команда (до 5–7 человек) и один продукт.
- Нет зрелой инфраструктуры: нет централизованных логов, трейсинга, метрик, CI/CD.
- Домен ещё не устоялся — границы будут постоянно меняться, а рефакторинг между сервисами в разы дороже, чем между модулями.
- Главный аргумент — «модно» или «у Netflix так».
Промежуточные варианты в экосистеме NestJS
NestJS позволяет двигаться постепенно:
- Модульный монолит с жёсткими границами модулей и явными контрактами.
- Hybrid Application — один процесс, но часть слушает HTTP, часть — брокер сообщений (
app.connectMicroservice()+app.startAllMicroservices()). - Извлечение одного сервиса через
ClientProxy— например, нотификации или биллинг выносим за брокер, остальное остаётся монолитом. - Полноценная микросервисная архитектура с API Gateway, service discovery, distributed tracing.
Senior-инженер выбирает минимальный шаг, который решает текущую боль, а не сразу прыгает на финальную стадию.
Что хочет услышать интервьюер
Понимание, что микросервисы — это организационное и инфраструктурное решение, а не только техническое
Конкретные триггеры перехода: нагрузка, SLA, независимый деплой, команды, bounded contexts
Знание возможностей NestJS: транспорты, hybrid app, ClientProxy, модульный монолит как стартовая точка
Понимание стоимости микросервисов: distributed tracing, eventual consistency, сетевые сбои, сложность тестирования
Умение предложить промежуточный путь (modular monolith → hybrid → extraction) вместо «всё или ничего»
Пример: Hybrid Application: HTTP + микросервис в одном процессе
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// подключаем транспорт брокера к существующему HTTP-приложению
// удобный промежуточный шаг перед полным разделением на сервисы
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.NATS,
options: { servers: ['nats://localhost:4222'] },
});
await app.startAllMicroservices();
await app.listen(3000);
}
bootstrap();
Пример: Извлечение одного домена через ClientProxy
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';
@Injectable()
export class OrdersService {
constructor(
// NOTIFICATIONS_SERVICE вынесен отдельно — у него своя нагрузка и SLA
@Inject('NOTIFICATIONS_SERVICE') private readonly client: ClientProxy,
) {}
async placeOrder(orderId: string) {
// event-паттерн: fire-and-forget, eventual consistency допустима
this.client.emit('order.placed', { orderId });
// message-паттерн: ждём ответ, когда нужен синхронный контракт
return firstValueFrom(
this.client.send<{ delivered: boolean }>('notify.user', { orderId }),
);
}
}
Типичные ошибки
Выбирать микросервисы по умолчанию, без анализа реальных проблем монолита
Игнорировать организационные факторы и закон Конвея — резать сервисы не по доменам, а по техническим слоям
Не учитывать стоимость инфраструктуры: observability, CI/CD, брокеры, service discovery
Считать, что NestJS «сам делает микросервисы» — забывая про распределённые транзакции и согласованность данных
Дробить монолит слишком рано, до стабилизации доменной модели, получая распределённый монолит


