Почему микросервисы не делят только по нагрузке
Что проверяют
Знаете ли вы, что границу сервиса задают данные и ответственность, а не профиль потребления ресурсов. Проверяют заодно, отличаете ли вы масштабирование от разделения: это разные задачи, и вторая нужна реже.
Как отвечать
Если резать по нагрузке, куски почти наверняка останутся связанными по данным. Получается распределённый монолит: общая база, согласованные релизы, цепочки сетевых вызовов вместо вызовов функций и транзакция, размазанная на сагу. Все минусы распределённой системы уже оплачены, ни одного плюса не получено.
Разумные критерии границы:
- Предметная область: сервис владеет своими данными, никто не лезет в его таблицы;
- Темп изменений: то, что меняется вместе, должно жить вместе;
- Владелец: у сервиса одна команда, иначе релиз превращается в переговоры;
- Изоляция отказов и требований: свои SLA, свой доступ к чувствительным данным, свой стек, если он действительно нужен.
Нагрузка тоже аргумент, но вторичный и обычно решаемый дешевле. В Go тяжёлый участок сначала выносят в пул воркеров и очередь, ограничивают конкурентность, добавляют кэш, поднимают больше реплик того же бинаря. Отдельный процесс из того же репозитория с другой точкой входа это ещё не микросервис, а режим запуска.
Признаки неверной границы стоит назвать самому: сервисы общаются на каждый чих, изменения всегда приходят парой, есть общие таблицы, а для простой операции нужна распределённая транзакция.
Где спотыкаются
Делят по слоям: сервис ручек, сервис бизнес-логики, сервис доступа к данным. Это тот же монолит, только вызовы функций заменены сетью, и релизить части по отдельности всё равно нельзя.
Путают масштабирование с разделением. Чтобы вытянуть горячий эндпоинт, нужны реплики и лимиты, а не новый репозиторий. Отдельный сервис оправдан, когда у куска своя модель данных и свой ритм изменений.
Не отвечают про данные. Сервисы формально разные, а база одна и таблицы общие: добавление колонки ломает соседа, схема становится общим интерфейсом, которым никто не управляет.