Микросервисы на TypeScript: архитектура и практика продвинутого уровня

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему TypeScript — идеальный выбор для микросервисов

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

В этой статье мы разберем продвинутые техники построения микросервисной архитектуры на TypeScript: от организации модульной структуры на базе NestJS до реализации транзакционных паттернов Saga и CQRS. Вы узнаете, как настроить эффективное межсервисное взаимодействие через RabbitMQ и gRPC, и получите готовые шаблоны кода для production-решений.

1. Архитектура микросервисов на NestJS: от монолита к распределенной системе

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

1.1. Базовая структура микросервиса

Каждый микросервис должен быть изолированным модулем с четкими границами. Пример минимальной структуры:

// user-service/src/main.ts
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.TCP,
      options: {
        host: '0.0.0.0',
        port: 3001,
      },
    },
  );
  await app.listen();
}
bootstrap();

Ключевой момент — использование MicroserviceOptions и строгая типизация транспорта. Это гарантирует, что при смене протокола (например, с TCP на Redis) компилятор подскажет, какие параметры нужно изменить.

1.2. Модульная организация и Dependency Injection

Для production-систем важно разделять бизнес-логику и инфраструктурный код. Используйте модули NestJS для группировки связанных компонентов:

// user-service/src/user/user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { DatabaseModule } from '../database/database.module';

@Module({
  imports: [DatabaseModule],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})
export class UserModule {}

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

2. Паттерны Saga и CQRS: управление распределенными транзакциями

В микросервисной архитектуре нельзя использовать традиционные ACID-транзакции. Вместо этого применяются паттерны Saga (для согласованности данных) и CQRS (для разделения чтения и записи).

2.1. Реализация Saga-паттерна на TypeScript

Saga — это последовательность локальных транзакций, где каждая неудача запускает компенсирующее действие. Рассмотрим пример с заказом и оплатой:

// order-service/src/sagas/create-order.saga.ts
import { Saga, ofType } from '@nestjs/cqrs';
import { Observable } from 'rxjs';
import { delay, map } from 'rxjs/operators';

export class CreateOrderSaga {
  @Saga()
  createOrder = (events$: Observable<any>): Observable<ICommand> => {
    return events$.pipe(
      ofType(OrderCreatedEvent),
      delay(1000),
      map(event => new ReserveInventoryCommand(event.orderId)),
    );
  };

  @Saga()
  handlePaymentFailure = (events$: Observable<any>): Observable<ICommand> => {
    return events$.pipe(
      ofType(PaymentFailedEvent),
      delay(500),
      map(event => new CancelOrderCommand(event.orderId)),
    );
  };
}

Здесь используется @nestjs/cqrs для реактивной обработки событий. Компенсирующие действия (CancelOrder) выполняются автоматически при сбое оплаты.

2.2. CQRS: разделение команд и запросов

Паттерн CQRS (Command Query Responsibility Segregation) позволяет оптимизировать производительность, разделяя модели для записи и чтения. Пример команды и обработчика:

// shared/commands/create-user.command.ts
export class CreateUserCommand {
  constructor(
    public readonly name: string,
    public readonly email: string,
  ) {}
}

// user-service/src/user/handlers/create-user.handler.ts
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';

@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
  async execute(command: CreateUserCommand): Promise<void> {
    // Логика создания пользователя
    console.log(`User created: ${command.name}`);
  }
}

Для запросов создаются отдельные Query-классы, что позволяет использовать разные источники данных (например, read-реплики базы данных).

3. Межсервисное взаимодействие: RabbitMQ и gRPC

Выбор протокола общения между сервисами — критическое архитектурное решение. Рассмотрим два популярных варианта: асинхронный (RabbitMQ) и синхронный (gRPC).

3.1. Асинхронное взаимодействие через RabbitMQ

RabbitMQ идеален для событийно-ориентированных систем. В NestJS настройка выглядит так:

// notification-service/src/main.ts
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.RMQ,
      options: {
        urls: ['amqp://localhost:5672'],
        queue: 'notifications_queue',
        queueOptions: {
          durable: true,
        },
      },
    },
  );
  await app.listen();
}
bootstrap();

Для отправки сообщений из другого сервиса используйте ClientProxy:

// order-service/src/order/order.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';

@Injectable()
export class OrderService {
  constructor(
    @Inject('NOTIFICATION_SERVICE') private client: ClientProxy,
  ) {}

  async createOrder(data: any) {
    // Логика создания заказа                

Блоги

Рекомендаций по книгам