В системе два модуля используют одну таблицу и независимо меняют её схему. Какой механизм связывает их эволюцию?
Общая таблица создаёт скрытую связанность через схему данных: изменение её структуры, ограничений или смысла полей требует согласования всех модулей, которые читают или изменяют таблицу. Поэтому модули остаются зависимыми даже при отсутствии прямых вызовов между ними.
Такая зависимость ограничивает независимые релизы, усложняет миграции и повышает риск того, что изменение одного модуля нарушит другой.
Централизованная база данных появилась как практичный способ хранить единое состояние, обеспечивать транзакционную целостность, устранять дублирование данных и выполнять общие запросы. Для монолитной системы это часто разумно: код и схема изменяются в рамках одного приложения и одной процедуры выпуска.
Проблема возникает при разделении системы на модули или сервисы. Физическое разделение кода ещё не формирует архитектурную автономность, если несколько частей продолжают напрямую зависеть от одной структуры хранения.
Предположим, модуль заказов использует таблицу заказов, а модуль аналитики читает её напрямую. Если модуль заказов переименует поле, изменит его смысл или заменит несколько значений одним новым состоянием, аналитика может начать выдавать неверные результаты либо перестать работать.
Риск связан не только с удалением столбцов. Даже расширение схемы может быть опасным, если меняются ограничения, порядок трактовки состояний, единицы измерения или правила заполнения. Схема становится фактически общим контрактом, хотя её владельцем формально считается только один модуль.
Сначала нужно определить владельца данных: модуль, который отвечает за их инварианты, жизненный цикл и допустимые изменения. Остальные модули не должны зависеть от внутренних таблиц владельца как от собственного хранилища.
Для чтения можно предоставить устойчивый контракт: API, представление с явно поддерживаемой схемой или отдельную проекцию данных. Для интеграции между автономными частями часто используют события, из которых потребитель строит собственное read-модель. Это уменьшает связанность, но переносит ответственность за задержки, повторную доставку и согласование версий.
Миграции общей схемы обычно выполняют по принципу расширить — перевести — удалить. Сначала добавляют совместимый элемент, затем переводят потребителей на него, после проверки удаляют старый элемент. Такой подход снижает риск несовместимого изменения, но временно увеличивает сложность схемы и требует контроля сроков удаления.
Полностью изолировать модули можно, выделив отдельное хранилище для каждого владельца и запретив прямой доступ к чужим таблицам. Цена этого решения — дублирование данных, необходимость синхронизации, более сложные запросы и возможная eventual consistency. Поэтому общая база не является автоматически ошибкой: она допустима, если совместная схема осознанно является частью общего контракта и управляется как таковая.
Главный критерий — не число баз данных, а возможность изменять модуль без чтения и записи чужих внутренних структур. Если изменение таблицы требует координации нескольких команд, общая схема уже выступает архитектурной границей, даже если это не отражено в коде.
В интернет-магазине модуль заказов и модуль аналитики читали одну таблицу. Команда заказов хотела заменить текстовое поле состояния на набор более точных переходов, но аналитические отчёты зависели от старых значений и периодически выполнялись во время миграции.
Рассматривались три варианта. Первый — оставить общую таблицу и договориться о ручной координации: это быстро, но сохраняет скрытую зависимость и делает каждый релиз межкомандным. Второй — дать аналитике отдельную базу с периодической полной загрузкой: граница становится яснее, однако отчёты получают задержку, а полные загрузки дороги. Третий — сделать заказы владельцем данных, публиковать изменения и поддерживать аналитическую проекцию: это требует обработки повторов и временной несогласованности, зато аналитика перестаёт зависеть от внутренней схемы заказов.
Выбрали третий вариант. Для перехода сначала запустили построение проекции из существующих данных, затем включили публикацию новых изменений и сверку результатов. После переключения аналитики внутреннюю таблицу заказов стали менять независимо; задержка обновления отчётов стала явной и контролируемой частью контракта.
Нет. В модульном монолите общая база может быть оправдана, если модули используют её через устойчивые контракты, схема имеет владельцев, а прямой доступ к чужим таблицам запрещён или строго ограничен. Ошибка — не сам факт совместного размещения данных, а неконтролируемая зависимость от чужой внутренней структуры.
Следует оценивать стоимость независимости. Раздельные хранилища увеличивают сложность согласования, резервного копирования, поиска и аналитики. Если независимые релизы не нужны, а единая транзакционная модель важнее, общая база может быть рациональным компромиссом.
Нет. Разные схемы помогают выразить владение и настроить права доступа, но сами по себе не удаляют зависимость, если один модуль продолжает читать таблицы другого. При изменении чужой таблицы потребитель всё равно должен синхронно адаптироваться.
Разделение схем становится полезным архитектурным механизмом только вместе с правилами владения, запретом прямого доступа и устойчивым способом получения данных. Иначе это лишь организационная маркировка физически общей структуры.
Нет. События убирают прямую зависимость от вызова и внутренней схемы отправителя, но оставляют зависимость от формата события, его семантики и гарантий доставки. Изменение значения события может так же нарушить потребителей, если контракт не версионируется и не поддерживается совместимость.
Кроме того, потребитель должен корректно обрабатывать повторы, изменение порядка и задержку событий, если такие свойства допускаются выбранной инфраструктурой. Поэтому события заменяют один вид связанности другим, обычно менее жёстким, но не делают систему полностью независимой.