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

Введение
Выбор между микросервисами и монолитом — один из самых обсуждаемых вопросов в современной разработке. От правильного решения зависит скорость разработки, стоимость инфраструктуры и способность команды поддерживать продукт в долгосрочной перспективе. В этой статье мы разберём обе архитектуры на конкретных примерах, обсудим критерии выбора и покажем типичные ошибки, которые совершают команды при переходе с одной модели на другую.
Главная ошибка — думать, что микросервисы это всегда «современно и правильно», а монолит — устаревший подход. На самом деле обе архитектуры активно используются крупными компаниями, и выбор зависит от размера команды, зрелости продукта и конкретных бизнес-задач.
Что такое монолит
Монолитное приложение — это единая кодовая база, которая собирается и деплоится как одно целое. Все модули (аутентификация, заказы, оплата, уведомления) живут в одном процессе и обращаются друг к другу через прямые вызовы функций.
// Пример монолитного сервиса заказов
class OrderService {
constructor(
private userService: UserService,
private paymentService: PaymentService,
private emailService: EmailService,
) {}
async createOrder(userId: string, items: Item[]) {
// Все вызовы внутри одного процесса
const user = await this.userService.findById(userId);
const payment = await this.paymentService.charge(user, items);
await this.emailService.sendConfirmation(user.email);
return { user, payment };
}
}
Плюсы монолита: простой деплой, низкая задержка вызовов, лёгкая отладка, понятная транзакционность через одну БД. Минусы: при росте кодовой базы любое изменение требует пересборки и регресс-тестирования всего приложения, а команды начинают мешать друг другу.
Что такое микросервисы
Микросервисы — это набор независимых сервисов, каждый из которых отвечает за свою бизнес-область и общается с другими через сеть (HTTP, gRPC, очереди сообщений).
// Сервис заказов вызывает другие сервисы по HTTP
class OrderService {
async createOrder(userId: string, items: Item[]) {
// Сетевой вызов вместо прямого
const user = await fetch(`http://users-service/api/users/${userId}`)
.then((r) => r.json());
// Платёжный сервис — отдельный процесс
const payment = await fetch('http://payments-service/api/charge', {
method: 'POST',
body: JSON.stringify({ userId, items }),
}).then((r) => r.json());
// Уведомления — асинхронно через очередь
await this.queue.publish('order.created', { userId, items });
return { user, payment };
}
}
Плюсы: независимый деплой команд, изоляция отказов, возможность использовать разные технологии для разных сервисов, горизонтальное масштабирование под нагрузку. Минусы: сложность распределённых транзакций, необходимость в наблюдаемости (трейсинг, логи, метрики), сетевые задержки и стоимость инфраструктуры.
Когда выбирать монолит
Монолит — оптимальный выбор для большинства новых проектов и стартапов. Если команда меньше 10 человек, бизнес-домен ещё не устоялся и нагрузка предсказуема — микросервисы создадут больше проблем, чем решат.
// Модульный монолит — компромисс между подходами
// Каждый модуль имеет чёткие границы, но живёт в одном процессе
import { OrdersModule } from './modules/orders';
import { UsersModule } from './modules/users';
import { PaymentsModule } from './modules/payments';
const app = new Application();
app.register(UsersModule);
app.register(OrdersModule);
app.register(PaymentsModule);
Модульный монолит позволяет позже легко выделить отдельный модуль в микросервис, если возникнет такая необходимость.
Когда выбирать микросервисы
Микросервисы оправданы, когда команда выросла до 50+ человек и разные части продукта развиваются независимыми темпами. Например, поиск нужно масштабировать сильнее, чем профиль пользователя, а команда уведомлений хочет переписать всё на Go.
# docker-compose для микросервисной системы
services:
users-service:
image: users:latest
# Собственная база данных
depends_on: [users-db]
orders-service:
image: orders:latest
depends_on: [orders-db, message-broker]
api-gateway:
image: gateway:latest
# Единая точка входа для клиентов
ports: ['80:80']
Ключевой признак готовности к микросервисам — наличие DevOps-культуры, CI/CD и инструментов наблюдаемости. Без этого микросервисы превращаются в распределённый хаос.
Частые ошибки
Первая ошибка — выбор микросервисов «потому что модно». Команда из пяти человек тратит месяцы на инфраструктуру вместо разработки продукта.
Вторая ошибка — неправильные границы сервисов. Если для одной бизнес-операции нужно вызвать пять сервисов, это не микросервисы, а распределённый монолит с худшими свойствами обоих подходов.
Третья ошибка — игнорирование распределённых транзакций. В монолите транзакция БД защищает от частичных сбоев, а в микросервисах нужны паттерны Saga или outbox.
// Outbox-паттерн: событие сохраняется в той же транзакции,
// что и бизнес-данные, а отправляется отдельным процессом
async function createOrder(data: OrderData) {
await db.transaction(async (trx) => {
const order = await trx('orders').insert(data);
// Событие в той же транзакции — гарантия согласованности
await trx('outbox').insert({
event: 'order.created',
payload: JSON.stringify(order),
});
});
}
Четвёртая ошибка — отсутствие версионирования API между сервисами. Изменение контракта ломает соседние команды.
Заключение
Не существует «правильной» архитектуры — есть подходящая для конкретной задачи. Начинайте с модульного монолита: он быстрее в разработке и проще в поддержке. Переходите на микросервисы только когда упрётесь в реальные ограничения: организационные, технические или нагрузочные.
Главный принцип — архитектура должна следовать за командой и продуктом, а не наоборот. Лучше иметь работающий монолит, чем сломанные микросервисы.






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