Microservices: обзор

Микросервисная архитектура — подход к построению приложений как набора небольших, независимо разворачиваемых сервисов, каждый из которых отвечает за одну бизнес-область и общается с другими через HTTP API или message queue.

Зачем нужно

Монолит удобен на старте, но при росте команды и кодовой базы замедляет деплой (весь монолит пересобирается при изменении одного модуля), усложняет масштабирование (нельзя масштабировать только перегруженный компонент) и создаёт конфликты при параллельной разработке. Микросервисы решают эти проблемы за счёт независимости сервисов — каждый деплоится, масштабируется и обновляется отдельно.

Где используется

  • Крупные продукты с несколькими командами (e-commerce, SaaS)
  • Сервисы с разными нагрузочными профилями (API-gateway vs worker)
  • Когда нужен polyglot: часть сервисов на Node.js, часть на Python (ML)
  • В связке с Docker, Kubernetes для оркестрации

Основной контент

Монолит vs Микросервисы

Монолит:                    Микросервисы:
┌─────────────────┐         ┌──────────┐  ┌──────────┐
│                 │         │  Users   │  │ Products │
│  Users          │         │ Service  │  │ Service  │
│  Products       │  →→→    └──────────┘  └──────────┘
│  Orders         │              ↕              ↕
│  Auth           │         ┌──────────┐  ┌──────────┐
│                 │         │  Orders  │  │   Auth   │
└─────────────────┘         │ Service  │  │ Service  │
  1 деплой, 1 БД            └──────────┘  └──────────┘
                             Отдельный деплой, своя БД

Способы коммуникации

// 1. Синхронный — HTTP REST (axios/fetch)
// users-service вызывает orders-service
const { data: orders } = await axios.get(
  `${process.env.ORDERS_SERVICE_URL}/api/orders?userId=${userId}`
);

// 2. Асинхронный — Message Queue (Bull/RabbitMQ)
// Публикуем событие, orders-service подпишется сам
await eventBus.publish('user.created', { userId, email });

// 3. gRPC — бинарный протокол (высокая производительность)
// Используется в Google, Netflix

Пример простого сервиса

// users-service/app.js
const express = require('express');
const app = express;
app.use(express.json());

// Каждый сервис имеет свою БД
const db = require('./db'); // только users таблица

app.get('/api/users/:id', async (req, res) => {
  const user = await db('users').where({ id: req.params.id }).first;
  if (!user) return res.status(404).json({ error: 'Not found' });
  res.json(user);
});

app.listen(3001, () => console.log('Users service on 3001'));

API Gateway

// gateway/app.js — единая точка входа
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express;

// Маршрутизация к сервисам
app.use('/api/users', createProxyMiddleware({ target: 'http://users-service:3001' }));
app.use('/api/orders', createProxyMiddleware({ target: 'http://orders-service:3002' }));
app.use('/api/products', createProxyMiddleware({ target: 'http://products-service:3003' }));

app.listen(3000, () => console.log('Gateway on 3000'));

Docker Compose для локальной разработки

# docker-compose.yml
version: '3'
services:
  gateway:
    build: ./gateway
    ports: ['3000:3000']

  users-service:
    build: ./users-service
    environment:
      DATABASE_URL: postgres://postgres:postgres@users-db/users
    depends_on: [users-db]

  users-db:
    image: postgres:15
    environment:
      POSTGRES_DB: users

Частые ошибки

  • Преждевременная декомпозиция — начинать с микросервисов на старте проекта, когда границы доменов ещё неясны; лучше начать с монолита
  • Распределённые транзакции — обновление данных в нескольких сервисах атомарно — сложная задача; нужен паттерн Saga
  • Синхронные цепочки вызовов — если сервис A вызывает B, который вызывает C: медленно и ненадёжно; переходить на async через очереди
  • Нет service discovery — хардкодить IP-адреса сервисов нельзя; использовать DNS, Consul или Kubernetes Services

Связанные темы

Ресурсы