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: движение · хранение · учёт.
Владение сущностями:
| Сущность | Владелец | Кто читает |
|---|---|---|
| Партия, место в партии, маршрут | движение |
хранение при приёмке |
| Коробка на полке, ячейка, срок хранения | хранение |
учёт при сверке |
| Персональные данные получателя | хранение, единственный владелец |
Никто: остальные работают с идентификатором |
| Операция (журнал) | учёт, единственный пишущий |
Все — через вызов, не через таблицу |
| Справочники пунктов и сотрудников | движение |
Все на чтение |
Три шага перехода:
- Обобщить журнал — запись произвольной операции со схемой в данных, вместо таблицы на тип операции. После этого связность пересчитывается.
- Развести ПДн — убрать ФИО и телефон из журнала и отчётности, оставить идентификатор. Это же снимает дисквалификатор о классе защищаемых данных на будущее.
- Включить запрет на чужие таблицы — проверка на сборке плюс раздельные учётные записи БД по модулям. Без этого границы существуют только на бумаге.
Обоснование#
Гранулярность решена дисквалификаторами, а не сравнением — четыре против сервисов, и три из них организационные, как и предсказывает мастер-шаблон.
Модульность решена совпадением принципов. Взяты линии, на которых сошлись по два принципа: склад ↔ пункт (язык и доступность) и выделение журнала (язык и владение данными). Отчётность оставлена внутри учёта: её отделял только кандидат B, набравший 58 % — то есть единственный принцип, ни с чем не совпавший.
Связность 71 % против порога 80 % не отменяет линию A, потому что измеренная величина объясняется устройством журнала, а не положением линии, и первый шаг перехода её меняет. Порог записан в условия пересмотра как проверяемое обязательство, а не как достигнутый факт.
C не выбран, хотя связность у него выше. 86 % достигнуты ценой того, что журнал остаётся общим — то есть ценой дисквалификатора. Более высокое число получено за счёт непроведённой границы.
Последствия#
Отрицательные:
- Три шага перехода — работа без видимого результата. Ни один не сокращает 90 суток до обнаружения расхождения, ради которых всё затевалось. Их придётся защищать перед операционным блоком отдельно.
- Обобщение журнала — правка на пути каждой операции, при трёх сопровождающих и выкате раз в две недели вручную. Это самый рискованный из трёх шагов, и его обратимость проверяется отдельно.
- Запрет на чужие таблицы вскроет неизвестное. Три учётные записи с правом записи, которые никто не может опознать, начнут падать. Это не побочный эффект, а способ их найти — но падать они будут в проде.
- Цель по доступности склада понижена с 99,9 % до 99,5 % и остаётся такой, пока квант не разделён. Остановка склада останавливает сеть на следующий день — это обязательство перед операционным блоком, а не техническая деталь.
- 16 % файлов не отнесены ни к одному модулю — код коробочного продукта. Он остаётся общим и вне границ, то есть каждая граница дырява ровно на эту долю.
Положительные: цена ошибки в границе — дни, а не квартал; все четыре силы интеграции оплачены нулём; сверка получает единственный источник — обобщённый журнал с одним пишущим.
Условия пересмотра#
- Связность после обобщения журнала не поднялась выше 80 % — линия A ложная, вернуться к сравнению.
- Появилась вторая команда-владелец.
- Появилась наблюдаемость: метрики по предметной области, трассировка, автоматический выкат — снимается главный дисквалификатор против сервисов.
- У сущности появился второй пишущий.
- Профиль нагрузки частей разошёлся на порядок — сейчас разницы нет вовсе.
- Цель по доступности склада вернули к 99,9 % — тогда квант обязан делиться, и решение о сервисах открывается заново.
Самая дорогая будущая граница#
Линия вокруг персональных данных получателя. Сегодня ПДн размазаны по записи посылки, журналу выдачи и отчётности; шаг 2 собирает их в хранение. Если этого не сделать сейчас, то через год любая граница — сервисная или регуляторная — будет проходить через три места сразу, и стоимость режима защиты умножится на три. Технически это самый дешёвый из трёх шагов и самый дорогой из отложенных.
Связанные ADR#
Зависимые: решение о декомпозиции данных (этап 3), о хранилище журнала (этап 4), о целях уровня обслуживания (этап 6 — с поправкой 99,5 %).