Владение данными и состоянием

Что это такое

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

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

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

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

Один писатель, много читателей

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

сервис заказов: пишет заказы, владеет правилами оформления
сервис отчётов: держит модель чтения, заказы никогда не пишет

Общая схема — это общее развёртывание

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

сервис A --\
            +--> одна схема --> каждая миграция — совместный релиз
сервис B --/

Транзакции останавливаются на границе

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

Согласованность становится вопросом времени

Как только копия существует, она устаревает с момента создания. Проектный вопрос не в том, происходит ли это, а в том, что система делает во время окна: должно ли чтение копии быть актуальным или «достаточно свежего» хватает? Явный ответ превращает класс запутанных ошибок в задокументированное свойство и подсказывает, где ставить повторные попытки, версии или путь чтения собственных записей.

писатель -> событие -> проектор -> модель чтения   (запаздывание: миллисекунды — секунды)

Копируйте данные осознанно, а не случайно

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

Практика

Найдите второго писателя

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

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

Пересоберите производное хранилище

Возьмите модель чтения или кэш, производный от источника истины. Удалите его и пересоберите из источника.

Готово, когда: пересобранное хранилище даёт те же ответы и вы убедились, что ничего не существует только в копии.

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

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

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