Введение: Почему TypeScript — идеальный выбор для микросервисов
Современная разработка backend-систем всё чаще отходит от монолитов в пользу микросервисной архитектуры. TypeScript, будучи строго типизированной надстройкой над JavaScript, предлагает уникальное сочетание гибкости и надёжности. Статическая типизация на этапе компиляции позволяет отлавливать ошибки интеграции между сервисами до того, как они попадут в продакшн, а богатая экосистема (NestJS, Express, Fastify) даёт готовые инструменты для построения распределённых систем.
В этой статье мы разберём полный цикл: от проектирования архитектуры до деплоя микросервисов на TypeScript. Вы узнаете, как организовать межсервисное взаимодействие, настроить брокеры сообщений и обеспечить отказоустойчивость.
1. Архитектура микросервисов на TypeScript: ключевые принципы
1.1 Декомпозиция предметной области
Первый шаг — разбиение бизнес-логики на независимые сервисы. Каждый микросервис отвечает за свою ограниченную область (Bounded Context). В TypeScript это естественным образом выражается через модули и пакеты.
Пример декомпозиции для интернет-магазина:
- Auth Service — аутентификация и JWT-токены
- Product Service — каталог товаров и цены
- Order Service — оформление и история заказов
- Payment Service — обработка платежей
Каждый сервис — это отдельное приложение на TypeScript со своим package.json, конфигурацией и точкой входа (main.ts).
1.2 Выбор технологического стека
Для быстрого старта рекомендуется использовать NestJS — фреймворк, из коробки поддерживающий микросервисные паттерны. Он предоставляет транспортные адаптеры для TCP, Redis, RabbitMQ, Kafka и gRPC.
Базовый стек:
- Язык: TypeScript (строгий режим, strict: true)
- Фреймворк: NestJS
- Брокер сообщений: RabbitMQ (через @nestjs/microservices)
- База данных: PostgreSQL (каждый сервис — свою БД)
- Контейнеризация: Docker + Docker Compose
1.3 Паттерны взаимодействия
Различают два основных подхода: синхронный (HTTP/gRPC) и асинхронный (очереди). Для критичных по времени операций используйте синхронные запросы, для фоновых задач — очереди.
Пример настройки гибридного сервиса в NestJS:
// main.ts — точка входа микросервиса
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
// HTTP сервер для REST API
const app = await NestFactory.create(AppModule);
// Подключение RabbitMQ для асинхронного обмена
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'orders_queue',
queueOptions: { durable: true },
},
});
await app.startAllMicroservices();
await app.listen(3000);
}
bootstrap();
2. Практическая реализация: от модели данных до API
2.1 Создание общего пакета с типами
Чтобы избежать дублирования интерфейсов между сервисами, вынесите общие типы в отдельный npm-пакет (например, @mycompany/shared-types). Это ключевое преимущество TypeScript — единый источник правды для всех сервисов.
// shared-types/src/dto/order.dto.ts
export interface CreateOrderDto {
userId: string;
productId: string;
quantity: number;
totalPrice: number;
}
export interface OrderResponse {
id: string;
status: 'pending' | 'confirmed' | 'cancelled';
createdAt: Date;
}
2.2 Реализация Product Service (REST API)
Сервис каталога товаров. Используем TypeORM для работы с PostgreSQL и декораторы NestJS для валидации.
// product.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from './product.entity';
@Injectable()
export class ProductService {
constructor(
@InjectRepository(Product)
private productRepo: Repository<Product>,
) {}
async getProduct(id: string): Promise<Product> {
return this.productRepo.findOneBy({ id });
}
async createProduct(data: Partial<Product>): Promise<Product> {
const product = this.productRepo.create(data);
return this.productRepo.save(product);
}
}
2.3 Асинхронное взаимодействие через RabbitMQ
При создании заказа Order Service отправляет событие в очередь, а инвентарный сервис его обрабатывает.
Отправка события (Order Service):
// order.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';
@Controller('orders')
export class OrderController {
private client: ClientProxy;
constructor() {
this.client = ClientProxyFactory.create({
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'inventory_queue',
},
});
}
@Post()
async createOrder(@Body() createOrderDto: any) {
// Сохраняем заказ в БД...
// Отправляем событие в очередь
this.client.emit('order_created', createOrderDto);
return { status: 'processing' };
}
}
Обработка события (Inventory Service):
// inventory.controller.ts
import { Controller } from '@nestjs/common';
import { EventPattern, Payload } from '@nestjs/microservices';
@Controller()
export class InventoryController {
@EventPattern('order_created')
async handleOrderCreated(@Payload() data: any) {
console.log('Резервируем товар:', data.productId);
// Логика уменьшения остатков...
}
}
3. Отказоустойчивость и мониторинг
3.1 Circuit Breaker и Retry
Для синхронных вызовов между сервисами используйте паттерн Circuit Breaker. В TypeScript это реализуется через библиотеку cockatiel или встроенные механизмы NestJS.
import { CircuitBreakerPolicy }