Реалізація Microservices Architecture для веб-додатку
Мікросервісна архітектура — розбиття моноліту на незалежно розгортувані сервіси, кожен з яких відповідає за свою бізнес-область. Кожен сервіс має власну БД, власний деплой та власну команду. Це не про масштаб запитів — це про масштаб команди та частоту змін.
Коли переходити на мікросервіси
Мікросервіси вирішують організаційні проблеми, а не технічні. Ознаки готовності:
- 3+ команди розробляють один моноліт та заважають один одному
- Різні частини системи потребують різного масштабування
- Критичні частини (платежі, сповіщення) потребують незалежного розгортання
- Різні технологічні стеки виправдані для різних завдань
Моноліт з хорошою архітектурою часто краще передчасної декомпозиції.
Декомпозиція на сервіси
По бізнес-можливостях (Business Capability):
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ User Svc │ │ Product Svc │ │ Order Svc │ │ Payment Svc │
│ │ │ │ │ │ │ │
│ Auth │ │ Catalog │ │ Cart │ │ Stripe │
│ Profiles │ │ Search │ │ Checkout │ │ Refunds │
│ Permissions │ │ Inventory │ │ History │ │ Invoices │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
│ │ │ │
└─────────────────┴─────────────────┴─────────────────┘
Message Bus (Kafka)
Кожен сервіс — своя PostgreSQL (або MongoDB, Redis, де доцільно). Немає спільних БД між сервісами.
Міжсервісна комунікація
Синхронна (REST/gRPC) — запит-відповідь, підходить для запитів користувача:
// Order Service викликає Product Service для перевірки наявності
const productClient = new ProductServiceClient(process.env.PRODUCT_SERVICE_URL);
async function createOrder(items: OrderItem[]) {
// Перевіряємо наявність товарів синхронно
const availability = await productClient.checkAvailability(
items.map(i => ({ productId: i.productId, quantity: i.quantity }))
);
if (availability.some(a => !a.available)) {
throw new InsufficientStockError();
}
// ...
}
Асинхронна (Events/Kafka) — для операцій, які не потребують негайної відповіді:
// Order Service публікує подію після створення замовлення
await kafka.producer.send({
topic: 'order.events',
messages: [{
key: order.id,
value: JSON.stringify({
type: 'OrderCreated',
orderId: order.id,
customerId: order.customerId,
items: order.items,
total: order.total,
occurredAt: new Date().toISOString()
})
}]
});
// Notification Service підписаний на 'order.events'
kafka.consumer.on('order.events', async (event) => {
if (event.type === 'OrderCreated') {
await notificationService.sendConfirmationEmail(event.customerId, event.orderId);
}
});
Strangler Fig Патерн для міграції моноліту
Поступова міграція без «великого переписування»:
- Визначити найбільш ізольований модуль моноліту (зазвичай — сповіщення, пошук або аутентифікація)
- Поставити прокси (API Gateway) перед моноліту
- Вивести модуль у окремий сервіс
- Переключити прокси на новий сервіс
- Видалити код з моноліту
- Повторити для наступного модуля
# API Gateway (nginx) маршрутизує за шляхом
location /api/auth/ {
proxy_pass http://auth-service:3001;
}
location /api/notifications/ {
proxy_pass http://notification-service:3002;
}
location /api/ {
proxy_pass http://monolith:8080; # решта в моноліт
}
Data Management
Database per Service — кожен сервіс володіє своїми даними:
# docker-compose.yml
services:
user-db:
image: postgres:15
environment:
POSTGRES_DB: users
order-db:
image: postgres:15
environment:
POSTGRES_DB: orders
product-db:
image: postgres:15
environment:
POSTGRES_DB: products
notification-db:
image: redis:7
Saga Pattern для розподілених трансакцій (див. окрему сторінку).
Shared Data через API — якщо Order Service потрібні дані про користувача, він запитує User Service через API, не пише в його БД.
Інфраструктура
| Компонент | Інструмент |
|---|---|
| Container orchestration | Kubernetes |
| API Gateway | Kong, Traefik, AWS API Gateway |
| Service Discovery | Consul, Kubernetes DNS |
| Config Management | Consul KV, Vault |
| Message Broker | Apache Kafka, RabbitMQ |
| Distributed Tracing | Jaeger, Zipkin |
| Centralized Logging | ELK Stack, Loki + Grafana |
| Health Checks | Kubernetes liveness/readiness probes |
Спостережуваність
Кожен сервіс повинен експортувати:
- Метрики в Prometheus (RED: Rate, Errors, Duration)
- Трейси в Jaeger (OpenTelemetry SDK)
- Логи у структурованому JSON → Loki або Elasticsearch
// OpenTelemetry трейсинг в Node.js
import { trace, context } from '@opentelemetry/api';
const tracer = trace.getTracer('order-service');
async function processOrder(orderId: string) {
const span = tracer.startSpan('processOrder');
span.setAttribute('order.id', orderId);
try {
await context.with(trace.setSpan(context.active(), span), async () => {
await validateOrder(orderId); // дочірній span
await chargePayment(orderId); // дочірній span
await notifyCustomer(orderId); // дочірній span
});
span.setStatus({ code: SpanStatusCode.OK });
} catch (err) {
span.recordException(err);
span.setStatus({ code: SpanStatusCode.ERROR });
throw err;
} finally {
span.end();
}
}
Терміни реалізації
- Декомпозиція моноліту та виділення першого сервісу — 3–6 тижнів
- Налаштування інфраструктури (Kubernetes + Kafka + трейсинг) — 2–4 тижні паралельно
- Повна міграція середнього моноліту (5–10 сервісів) — 4–8 місяців
- Поступова міграція через Strangler Fig — 1–2 роки для крупного моноліту







