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