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