Сервис вырос: когда выделять второй процесс, а когда оставить модуль

У интернет-магазина один Go-сервис: он принимает заказы, считает стоимость доставки и формирует выгрузки для бухгалтерии. Команда предлагает разделить его на три микросервиса. Кажется, что независимые процессы автоматически сделают код понятнее. Но прежде полезно спросить, какую конкретную помеху должно убрать разделение. Ответ определит границу, способ общения и цену нового отказа.

Опишите затруднение через рабочую ситуацию

Предположим, выгрузка загружает процесс вычислениями, а ответы покупателям замедляются. Здесь нужно отделить ресурсы выполнения. Другой случай: изменения правил доставки вынуждают выпускать весь магазин, хотя этим занимается отдельная команда. Здесь интересует независимость изменений. Третий случай: файл с обработчиками стал слишком большим. Его размер сам по себе ещё не требует сети: разделение пакетов и зависимостей можно проверить внутри одного процесса.

Возьмите одно действие пользователя — оформление заказа — и выпишите, какие решения должны остаться согласованными. Например, цена, адрес и возможность доставки определяют, можно ли принять заказ. Теперь нарисуйте последовательность вызовов. Если после выделения доставки каждый запрос всё равно ждёт этот сервис, у магазина появилась новая обязательная зависимость. Отдельный репозиторий не уменьшил число условий, необходимых для успешного оформления.

Проверьте границу сначала в коде

Можно выделить расчёт доставки в пакет с узким интерфейсом: на вход идут адрес, состав заказа и нужные для тарифа параметры; на выход — стоимость, срок действия расчёта и причина отказа. Обработчик заказа больше не читает внутренние таблицы доставки. Такой шаг выявляет настоящий контракт до того, как появятся сериализация, таймауты и сетевые ошибки.

Если интерфейс постоянно требует передавать внутренние структуры магазина или позволяет менять их напрямую, граница пока нестабильна. Возникает полезный вопрос: какое знание действительно принадлежит доставке? Например, складская доступность может принадлежать другому участку системы. Ответ находится в правилах бизнеса и ответственности команды, а не в числе строк пакета. Начните с выделения бизнес-возможности: запишите, какие решения доставка принимает самостоятельно и какие сведения ей действительно нужны извне.

Сравните два варианта на одном сбое

Пусть внешняя служба тарифов недоступна. В модульном приложении ваш код решает, можно ли использовать действующий расчёт, сохранить черновик заказа или вернуть отказ. После выделения отдельного процесса к этому добавляется вопрос: недоступен тарифный провайдер или сам сервис доставки? Клиент должен получить различимый результат, а команда — данные для диагностики. Переход через сеть делает этот договор обязательным.

Для выгрузок решение может оказаться другим. Если пользователь готов получить файл позже, работа естественно отделяется по времени. API принимает заявку, фоновой исполнитель строит файл, пользователь получает состояние задания. Но теперь нужно хранить заявку и результат между перезапусками, определить повторное выполнение и срок хранения файла. Просто запустить goroutine после ответа пользователю недостаточно: процесс может закончиться вместе с обновлением.

Разница между расчётом тарифа и выгрузкой объясняет, почему нельзя выбирать архитектуру сразу для всех частей проекта. У них разные требования к ожиданию, повтору и ресурсам. Выгрузку можно вынести ради изоляции вычислений, а расчёт оставить модулем, пока он тесно связан с оформлением. В обоих случаях команда получает проверяемую пользу без обязательного одновременного разделения всего приложения.

Примите решение, которое можно пересмотреть

Запишите ожидаемое изменение после выделения: например, выпуск правил доставки проходит без выпуска магазина либо тяжёлая выгрузка не занимает его ресурсы. Рядом зафиксируйте, что появится в эксплуатации: второй процесс, контракт, журнал запросов, способ проверки готовности и обработка недоступности. Назначьте владельца изменений и владельца инцидента. Если ответственность остаётся общей и любой релиз требует синхронного обновления всех частей, самостоятельность пока существует только в схеме.

Перед переносом выполните короткую репетицию. Вызовите будущую зависимость через интерфейс, подмените её медленной или ошибающейся реализацией и посмотрите на поведение оформления заказа. Этот эксперимент не измеряет сеть, зато заставляет определить допустимые ответы. После реального выделения добавьте проверки контракта и сценарии частичной недоступности. Так решение опирается на наблюдаемое поведение проекта.

После выбора границы проверьте отмену зависимого HTTP-вызова. Если соединение оборвалось после меняющего запроса, потребуется ещё и правило безопасного повтора: новый процесс добавляет ситуации с неизвестным исходом.

Разобрать цену сети на практике

Если интерфейсы уже выделены и пора связывать процессы, платный курс по микросервисам на Go начинает с границ сервисов и цены разделения, затем ведёт к HTTP, gRPC, PostgreSQL и Kafka. В программе есть дедлайны, повторы, outbox/inbox, проверка отказов и запуск через Docker Compose. Вы сможете проследить, что осталось в базе и где начинается восстановление после сбоя. Откройте программу и начните с первого модуля о взаимодействии двух Go-программ.

Продолжить на практике

  • Микросервисы на Go: HTTP, PostgreSQL, gRPC, Kafka и Docker ↗

    От работающего Go API — к взаимодействию сервисов, которому можно доверять. Разберитесь, как проектировать контракты, сохранять согласованность данных и восстанавливать обработку после сбоев. HTTP, PostgreSQL, gRPC, Kafka и Docker — через код и наглядные разборы.