Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Границы сервисов: декомпозиция по домену

Границы сервисов: как резать систему по домену, а не по слоям

Декомпозиция сервисов: по бизнес-возможностям и поддоменам, признаки неверной границы, распределённый монолит и закон Конвея.

Прямой ответ

Граница сервиса должна совпадать с границей бизнес-возможности, а не с техническим слоем. Разделение по слоям — «сервис контроллеров», «сервис бизнес-логики», «сервис доступа к данным» — даёт систему, где любое изменение затрагивает все сервисы сразу.

Два рабочих критерия декомпозиции:

Критерий Вопрос Пример
Бизнес-возможность Какую функцию компании реализует сервис? Оформление заказа, выставление счёта, доставка
Поддомен К какой части предметной области относится? Каталог, корзина, оплата

Практическая проверка границы — частота совместных изменений. Если два сервиса почти всегда меняются в одном коммите или в одной задаче, граница проведена неверно: вместо двух сервисов получился один, разнесённый по сети.

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

Признаки неверной границы:

  1. Задача требует изменения трёх сервисов. Граница режет сценарий пополам.
  2. Сервисы деплоятся вместе. Фактически это одно приложение с сетевыми вызовами.
  3. Общая база данных. Сервисы не могут менять схему независимо — см. статью про общую базу.
  4. Синхронная цепочка из пяти вызовов. Задержка складывается, а сбой любого звена ломает сценарий.
  5. Один сервис знает схему другого. Прямой доступ к чужим данным вместо обращения к владельцу.

Результат такого разделения называют распределённым монолитом: недостатки микросервисов присутствуют, а преимущества отсутствуют. О паттернах и антипаттернах — в статье про микросервисы.

Практика: как проводить границы

1. Начинайте крупно. Выделить сервис из модуля с чистой границей проще, чем объединить обратно два неудачно разделённых. О границах модулей — в статье про модульный монолит.

2. Используйте владение данными как критерий. Сервис владеет данными, которые создаёт и изменяет. Если два сервиса пишут в одну таблицу, граница неверна; подробнее — в статье про владение данными.

3. Учитывайте структуру команд. Закон Конвея работает в обе стороны: система повторяет структуру организации, и наоборот — границы сервисов определяют, как команды взаимодействуют. Сервис, который обслуживают две команды, будет источником конфликтов.

4. Проектируйте асинхронный обмен. Там, где допустима задержка, событие предпочтительнее синхронного вызова: оно снижает связанность и переживает временную недоступность потребителя.

5. Проверяйте границы на реальных задачах. Возьмите пять типовых изменений и посмотрите, сколько сервисов затрагивает каждое. Если регулярно больше одного — границы нужно пересмотреть.

6. Разделяйте ядро и вспомогательное. Поддомены делятся на ключевые (дают конкурентное преимущество), поддерживающие и типовые. Типовые задачи чаще покупают или используют готовые решения.

7. Фиксируйте причину разделения. Решение о границе должно быть записано с обоснованием — см. статью про архитектурные решения.

Рекомендации:

  1. Не разделяйте по технологии. «Сервис на Go» и «сервис на Python» — не границы; это способ получить два сервиса там, где нужен один.
  2. Измеряйте связанность до разделения. Число вызовов между модулями и совместные коммиты дают объективную картину.
  3. Оставьте возможность обратного хода. Слияние двух сервисов дороже разделения, поэтому сомнительные границы лучше не создавать.
  4. Проверяйте независимость деплоя. Если сервис нельзя выкатить без согласования с другим, независимость только на словах.
  5. Согласуйте контракт. Граница сервиса — это и граница API; о версионировании — в статье про версии API.

Практический сценарий: разделение по слоям дало трёхкратный рост задержки

Контекст: монолит разделили на три сервиса по слоям: приём запросов, бизнес-логика и доступ к данным. Целью было независимое масштабирование.

  1. Результат через месяц. Задержка p95 выросла с 180 до 540 миллисекунд. Один пользовательский запрос проходил через три сетевых перехода, каждый добавлял около 40 миллисекунд, плюс сериализация.
  2. Вторая проблема. Любое изменение бизнес-правила требовало правки двух сервисов: слой логики и слой данных менялись вместе, поскольку разделяли одну модель.
  3. Третья проблема. Независимое масштабирование не сработало: слой данных всегда был узким местом, и его масштабирование упиралось в базу, которая оставалась общей.
  4. Действия. (а) Вернули слои в одно приложение, сохранив разделение кода по модулям; (б) заново провели границы по бизнес-возможностям: заказы, оплата, доставка; (в) каждому сервису выделили собственную схему в базе; (г) синхронные вызовы между сервисами заменили событиями там, где допустима eventual consistency; (д) ввели проверку: задача не должна затрагивать больше одного сервиса.
  5. Результат. Задержка вернулась к 210 миллисекундам; доля задач, затрагивающих один сервис, выросла с 35 до 88 процентов.

Вывод: разделение по техническим слоям не создаёт независимости, потому что слои связаны по своей природе. Граница проходит там, где заканчивается бизнес-возможность и начинается другая.

FAQ

Сколько сервисов должно быть в системе? Столько, сколько нужно для независимого масштабирования и независимых релизов. Число не является целью; ориентир — размер команды и частота изменений.

Можно ли исправить неверную границу без остановки? Да: сначала объединяют обмен в явные контракты, затем переносят данные к владельцу, и только потом меняют развёртывание. Миграция legacy описана в статье про Strangler Fig.

Что делать, если бизнес-возможности пересекаются? Искать границу по владению данными и по инварианту. Если два сценария меняют одни данные в одной транзакции, они относятся к одному сервису.

Нужен ли отдельный сервис для интеграции с внешней системой? Часто да: у него свой цикл отказа, свой таймаут и своя модель данных. Такой сервис выступает адаптером и защищает основную модель от изменений извне.

Источники: microservices.io (decomposition patterns), Martin Fowler (ConwaysLaw) — ссылки в шапке.