Границы и владение

Что это такое

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

Почему это важно

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

Ключевые идеи

Проводите границу вокруг причины изменения

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

сервис биллинга: правила ценообразования, счета, записи о платежах
сервис каталога: товары, доступность

Каждый сервис владеет своими данными, и больше никто их не читает

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

Граница — это ещё и граница команды

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

Через границу проходят контракты, и им нужно версионирование

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

Распределённая система — это другая задача, а не большая

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

Атомарность, которую даёт база данных внутри одного сервиса, между двумя исчезает. Изменение, охватывающее оба, требует компенсирующего действия, saga с шагами и определённым откатом либо осознанного решения, что согласованность в конечном счёте приемлема. Притворяться, что это не так, значит получить систему, которая в тестах выглядит атомарной, а в продакшене дублирует списание.

Делить легко, а объединять трудно

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

Практика

Проведите границу и обоснуйте её

Возьмите небольшую систему с тремя областями ответственности и проведите одну границу сервиса, назвав вид изменения, которое затронет только одну сторону.

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

Найдите общую базу данных

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

Готово, когда: вы можете назвать конкретное изменение, которое сломает другой сервис, и сказать, кому должны принадлежать данные.

Частые ошибки

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

Что читать дальше