Антон Ларичев

Введение
Когда проект растёт, монолитное приложение постепенно превращается в тяжёлый и хрупкий кусок кода, который страшно деплоить и невозможно масштабировать по частям. Микросервисная архитектура — один из способов решить эту проблему: вместо одного большого приложения система разбивается на набор небольших независимых сервисов, каждый из которых отвечает за свою бизнес-задачу.
В этой статье разберём, что такое микросервисы простыми словами, чем они отличаются от монолита, как сервисы взаимодействуют друг с другом и с какими ошибками сталкиваются начинающие разработчики.
Монолит vs микросервисы
Монолитное приложение — это единая кодовая база, единый процесс и обычно одна база данных. Всё просто разрабатывать и деплоить, пока проект небольшой.
Микросервисная архитектура делит систему на отдельные приложения (сервисы), которые:
- разрабатываются и деплоятся независимо друг от друга;
- имеют собственную базу данных или хранилище;
- общаются между собой по сети через API или очереди сообщений;
- могут быть написаны на разных языках и технологиях.
Например, интернет-магазин можно разбить на сервисы: каталог товаров, корзина, оплата, уведомления, пользователи. Каждый сервис можно масштабировать, обновлять и разворачивать отдельно.
Основные принципы
При проектировании микросервисов важно соблюдать несколько правил:
- Единственная ответственность. Каждый сервис решает одну бизнес-задачу.
- Независимость данных. Сервисы не должны напрямую обращаться к чужой базе данных.
- Слабая связанность. Изменение одного сервиса не должно ломать другие.
- Автономность деплоя. Сервис можно обновить без остановки всей системы.
Пример: разделение на сервисы
Представим простой сервис заказов на Node.js с Express. Он отвечает только за создание заказов и обращается к сервису пользователей по HTTP, а не напрямую к его базе данных.
// order-service/index.js
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
// Создание заказа
app.post('/orders', async (req, res) => {
const { userId, items } = req.body;
// Проверяем пользователя через сервис пользователей
const userResponse = await axios.get(
`http://user-service:3001/users/${userId}`
);
if (!userResponse.data) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
const order = {
id: Date.now(),
userId,
items,
status: 'created',
};
// Сохраняем заказ в собственной базе сервиса заказов
res.status(201).json(order);
});
app.listen(3002, () => {
console.log('Order service запущен на порту 3002');
});
Обратите внимание: сервис заказов ничего не знает о внутреннем устройстве сервиса пользователей — только о его публичном API.
Взаимодействие сервисов
Сервисы могут общаться синхронно (через REST или gRPC) или асинхронно (через очереди сообщений вроде RabbitMQ или Kafka). Асинхронный подход снижает связанность и повышает отказоустойчивость.
// notification-service/consumer.js
const amqp = require('amqplib');
async function startConsumer() {
const connection = await amqp.connect('amqp://rabbitmq');
const channel = await connection.createChannel();
const queue = 'order_created';
await channel.assertQueue(queue);
// Слушаем события о новых заказах
channel.consume(queue, (message) => {
const order = JSON.parse(message.content.toString());
console.log(`Отправляем уведомление по заказу ${order.id}`);
channel.ack(message);
});
}
startConsumer();
Такой подход позволяет сервису уведомлений не блокировать создание заказа: если он временно недоступен, сообщение просто подождёт в очереди.
Запуск через Docker Compose
Для локальной разработки удобно поднимать все сервисы одной командой.
# docker-compose.yml
version: '3.8'
services:
user-service:
build: ./user-service
ports:
- '3001:3001'
order-service:
build: ./order-service
ports:
- '3002:3002'
depends_on:
- user-service
rabbitmq:
image: rabbitmq:3-management
ports:
- '5672:5672'
- '15672:15672'
Команда docker compose up поднимет все сервисы вместе, эмулируя реальную распределённую систему прямо на локальной машине.
Частые ошибки
- Слишком мелкое дробление. Десятки крошечных сервисов ради одной функции усложняют систему больше, чем упрощают.
- Общая база данных на все сервисы. Это сводит на нет независимость сервисов и создаёт скрытую связанность.
- Отсутствие мониторинга и трассировки. В распределённой системе без логов и трейсинга почти невозможно понять, какой сервис стал причиной ошибки.
- Синхронные цепочки вызовов. Если сервис A вызывает B, который вызывает C, любая задержка в C замедляет весь запрос. Стоит рассмотреть асинхронное взаимодействие.
- Переход на микросервисы на старте проекта. Для маленького MVP монолит почти всегда проще и быстрее в разработке.
Заключение
Микросервисная архитектура — это не универсальное решение, а инструмент, который решает конкретные проблемы масштабирования команды и системы. Она добавляет сложность в виде сетевого взаимодействия, распределённых данных и необходимости в мониторинге, но взамен даёт независимость деплоя, гибкость в выборе технологий и возможность масштабировать отдельные части системы.
Начинающим разработчикам стоит сначала хорошо разобраться в принципах модульного монолита и только затем переходить к микросервисам, когда для этого появится реальная необходимость.





Комментарии
0