Перейти к содержанию

ADR-0001 · Границы: модульный монолит и три модуля#

Статус:    принят · 2026-09-18
Метод:     D-02 v5.0 · шаблон ADR v1.2
Участники: сопровождение (3 человека), руководитель операционного блока

Учебная запись: часть прогона E-03, система вымышлена. Форма и рассуждение настоящие.

Контекст#

Вход — 00-context.md. Система работает одиннадцать лет: одна база, 340 таблиц, границ внутри нет.

Открыты оба вопроса — и гранулярность, и модульность. По D-02 сначала гранулярность: линию проводят внутри уже принятого числа частей.

Девять вопросов раздела 1#

Вопрос Ответ
Какая декомпозиция есть сейчас Никакой. Одна кодовая база, одна база данных, один выкат [схема]
Сколько изменений за 6 месяцев затронуло больше одного модуля Модулей нет — считать не с чем до построения кандидатов, см. замечание ниже
Какие операции обязаны выполняться атомарно Приёмка на пункте: пометить место в партии принятым, создать коробку на полке, записать операцию — сегодня одна транзакция [схема]. Выдача — то же тремя шагами
У каких сущностей больше одного пишущего Журнал операций пишут все пять сценариев — приёмка, выдача, возврат, сортировка, инвентаризация [схема]
Где одно слово означает разное Два случая, оба устойчивые — см. ниже [орг]
Сколько команд Одна, три человека. Ни один не автор [орг]
У каких частей профиль различается на порядок По нагрузке — нет, везде единицы операций в секунду. По доступности — да: склад 99,9 %, пункт 99,5 % [требования]
Что будет переписываться в ближайший год Роадмапа нет; заявлено одно — сократить время обнаружения расхождений с 90 суток до суток [продукт]
Данные особого режима защиты и как лежат относительно линий Персональные данные получателя — ФИО и телефон. Лежат в записи посылки, копируются в журнал выдачи и в отчётность [схема]

Язык разошёлся в двух местах, и оба раза по одной линии:

Термин На складе На пункте
«выдача» Передача партии курьеру Передача коробки получателю
«остаток» Мест в партии, не отправленных дальше Коробок на полках, не выданных

Замечание к методу: связность нельзя посчитать первой#

D-02 говорит: «Связность изменений считается первой. Пока она не посчитана, остальная матрица — разговор о вкусах», и оценивает это в час работы с историей трекера.

Здесь так не выходит, и причин три.

Препятствие Что с ним сделали
Трекер подрядчика закрыт в 2021, своя постановка нерегулярна: 268 коммитов за 6 месяцев против 94 задач [история 6 мес] Считаем по коммитам, а не по задачам. D-02 это допускает — вопрос звучит «история коммитов и задач»
Модулей не существует, и доля «уместившихся в одну границу» не определена ни для чего, кроме тривиальных 100 % Связность считается для каждого кандидата после его построения, а не до
Сопоставление файлов с кандидатами — три дня, а не час: 340 таблиц, владельцы неизвестны, 16 % файлов принадлежат коробочному продукту и не относятся ни к одному кандидату [схема] Сделано; покрытие 84 % файлов

Опасение «фактов нет» оказалось неточным, и различие важное. Нет истории происхождения — почему таблица появилась и кто её завёл. История изменений за нужный период есть и полна. D-02 спрашивает вторую, и она у нас есть.

Кандидаты#

Построены применением трёх принципов к собранным фактам.

Кандидат Принцип Части
0 · как есть — Одна граница
A · по языку Где термин меняет значение движение (склад, партии, маршрут) · хранение (пункт, полка, выдача) · учёт (журнал, сверка, отчётность)
B · по владению данными Один пишущий на сущность справочники · посылки · журнал · отчётность
C · по доступности Что изолируется отдельно склад (99,9 %) · пункт (99,5 %)

Линии совпали дважды, и это сильнейший сигнал. A и C сошлись на линии склад ↔ пункт; A и B сошлись на выделении журнала. Разошлись они только в том, отделять ли отчётность от журнала и куда девать справочники.

Проверка разреза по профилю нагрузки (D-02, раздел 4): есть ли у выделяемой части собственный язык, инварианты и владелец данных? У склада — да, по всем трём. Значит C — не кэш, названный сервисом, и его совпадение с A настоящее.

Дисквалификаторы#

Сработали шесть, и они делятся на три группы.

Против сервисов — то есть против самой гранулярности:

Условие Как выполнено
Нет наблюдаемости, автовыката и трассировки Логи 14 суток, метрики только инфраструктурные, трассировки нет, выкат раз в две недели вручную [орг]. Для микросервисов это входное требование, а не следствие
Сервисов больше, чем команд Любой кандидат даёт от двух частей при трёх сопровождающих, ни один из которых не автор [орг]
Разрез разрывает атомарную операцию основного сценария Линия склад ↔ пункт проходит ровно через приёмку: 225 тыс. раз в сутки, сегодня одной транзакцией [схема]
Разрез рассекает класс защищаемых данных ПДн получателя нужны при выдаче, попадают в журнал и в отчётность. При разрезе режим защиты держат три владельца вместо одного [схема]

Против линии — то есть против модульности:

Условие Как выполнено Чем снимается
Остаётся общая таблица Журнал операций пишут все пять сценариев [схема] Журнал становится модулем с единственным пишущим: остальные вызывают его, а не вставляют строку

Отдельно — дисквалификатор об архитектурном кванте, и он отсекает не разрез, а требование:

Склад и пункт живут в одном кванте — общая база, общий выкат, синхронная связь на каждой приёмке. Квант имеет одну доступность, какие бы числа ни стояли в требованиях. 99,9 % склада и 99,5 % пункта внутри общего кванта — это 99,5 % на обоих. Либо квант делится вместе с сервисами (запрещено четырьмя строками выше), либо цели выравниваются по худшей.

Выравниваем по 99,5 % и записываем это решением, а не оставляем расхождением между документами. Вход в D-10 на этапе 6 — уже с этой поправкой.

Рассмотренные варианты#

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

Ось Что записывать 0 · как есть A · по языку B · по владению C · по доступности
Связность изменений Доля коммитов в одной границе 100 % по определению — значения не имеет 71 % [история 6 мес] 58 % [история 6 мес] 86 % [история 6 мес]
Транзакционные разрывы Операции основного сценария поперёк границы и требуемая согласованность Нет Нет — внутри процесса Нет Нет
Владение данными Сущностей с более чем одним пишущим 1 — журнал [схема] 0 после обобщения журнала 0 1 — журнал остаётся общим [схема]
Языковая чистота Терминов с двойным значением внутри границы 2 — «выдача», «остаток» [орг] 0 — разрез строился по языку 2 — линия не по языку [орг] 0 [орг]
Организационное соответствие Команд на границу Неприменимо — одна команда, и она останется одной при любом исходе [орг] То же То же То же
Глубина синхронной цепочки Сетевых вызовов в основном сценарии 0 0 — модули, не сервисы 0 0
Стоимость перехода Шагов и что ломается 0 3 шага: обобщить журнал, развести ПДн, включить запрет на чужие таблицы [схема] 5 шагов — четыре границы вместо трёх 2 шага, журнал остаётся общим
Обратимость Что стоит переделать при ложной границе — Перенос между модулями — дни Дни Дни

Порог связности — около 80 % (D-02). C проходит, A не проходит, B далеко.

Причина, по которой A не проходит, названа и она устранима. Из 268 коммитов 64 (24 %) трогают предметный модуль и журнал одновременно [история 6 мес]. Разбор показал: журнал типизирован по операциям — каждая новая операция требует правки в двух местах. Связность здесь измеряет не линию, а устройство общего модуля.

Силы интеграции#

Встречная половина: что тянет части обратно.

Сила Чем измеряется Цена внутри монолита Цена при сервисах
Общий код 22 модуля коробочного продукта используют обе стороны линии склад ↔ пункт: авторизация, печать этикеток, справочники [схема] Ноль — общий модуль остаётся общим Дублировать (два расходящихся правила печати) · библиотека (синхронный выкат обеих сторон) · сервис (ещё одна граница). Коробочный код не версионируется вовсе
Отношения в данных 14 внешних ключей пересекают линию склад ↔ пункт, 31 — линию учёта [схема] Ноль — ключи остаются Денормализация плюс сверка. Сверка — предмет всего кейса, добавлять к ней ещё один источник расхождений нельзя
Рабочий процесс Приёмка читает партию на каждой операции: 225 тыс. раз в сутки [замер] Ноль — вызов функции 225 тыс. сетевых вызовов в сутки на пути, где сейчас транзакция
Транзакции Нужна ли ACID поперёк линии — Да — дисквалификатор выше

Все четыре силы оплачиваются даром внутри монолита и дорого между сервисами. Это и есть количественный ответ на вопрос, почему модули, а не сервисы: не «мы не готовы», а счёт, выставленный разрезом, в четырёх строках.

Решение#

Сервисов не будет. Модульный монолит с тремя модулями по кандидату A: движение · хранение · учёт.

Владение сущностями:

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

Три шага перехода:

  1. Обобщить журнал — запись произвольной операции со схемой в данных, вместо таблицы на тип операции. После этого связность пересчитывается.
  2. Развести ПДн — убрать ФИО и телефон из журнала и отчётности, оставить идентификатор. Это же снимает дисквалификатор о классе защищаемых данных на будущее.
  3. Включить запрет на чужие таблицы — проверка на сборке плюс раздельные учётные записи БД по модулям. Без этого границы существуют только на бумаге.

Обоснование#

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

Модульность решена совпадением принципов. Взяты линии, на которых сошлись по два принципа: склад ↔ пункт (язык и доступность) и выделение журнала (язык и владение данными). Отчётность оставлена внутри учёта: её отделял только кандидат B, набравший 58 % — то есть единственный принцип, ни с чем не совпавший.

Связность 71 % против порога 80 % не отменяет линию A, потому что измеренная величина объясняется устройством журнала, а не положением линии, и первый шаг перехода её меняет. Порог записан в условия пересмотра как проверяемое обязательство, а не как достигнутый факт.

C не выбран, хотя связность у него выше. 86 % достигнуты ценой того, что журнал остаётся общим — то есть ценой дисквалификатора. Более высокое число получено за счёт непроведённой границы.

Последствия#

Отрицательные:

  • Три шага перехода — работа без видимого результата. Ни один не сокращает 90 суток до обнаружения расхождения, ради которых всё затевалось. Их придётся защищать перед операционным блоком отдельно.
  • Обобщение журнала — правка на пути каждой операции, при трёх сопровождающих и выкате раз в две недели вручную. Это самый рискованный из трёх шагов, и его обратимость проверяется отдельно.
  • Запрет на чужие таблицы вскроет неизвестное. Три учётные записи с правом записи, которые никто не может опознать, начнут падать. Это не побочный эффект, а способ их найти — но падать они будут в проде.
  • Цель по доступности склада понижена с 99,9 % до 99,5 % и остаётся такой, пока квант не разделён. Остановка склада останавливает сеть на следующий день — это обязательство перед операционным блоком, а не техническая деталь.
  • 16 % файлов не отнесены ни к одному модулю — код коробочного продукта. Он остаётся общим и вне границ, то есть каждая граница дырява ровно на эту долю.

Положительные: цена ошибки в границе — дни, а не квартал; все четыре силы интеграции оплачены нулём; сверка получает единственный источник — обобщённый журнал с одним пишущим.

Условия пересмотра#

  • Связность после обобщения журнала не поднялась выше 80 % — линия A ложная, вернуться к сравнению.
  • Появилась вторая команда-владелец.
  • Появилась наблюдаемость: метрики по предметной области, трассировка, автоматический выкат — снимается главный дисквалификатор против сервисов.
  • У сущности появился второй пишущий.
  • Профиль нагрузки частей разошёлся на порядок — сейчас разницы нет вовсе.
  • Цель по доступности склада вернули к 99,9 % — тогда квант обязан делиться, и решение о сервисах открывается заново.

Самая дорогая будущая граница#

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

Связанные ADR#

Зависимые: решение о декомпозиции данных (этап 3), о хранилище журнала (этап 4), о целях уровня обслуживания (этап 6 — с поправкой 99,5 %).