Как доменная модель ломает и спасает архитектуру

Статья архитектурно-аналитической серии на примере лунной базы: как ошибка в доменной модели превращается в ошибку архитектуры, а хорошая модель помогает точнее разделять контексты, взаимодействия и ответственность

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

Если доменная модель смазана, слишком абстрактна или искусственно упрощена, архитектура почти неизбежно начинает закреплять эту ошибку:

  • в API;
  • в схеме данных;
  • в границах сервисов;
  • в правилах консистентности;
  • в логике интеграции;
  • в ownership команд.

Поэтому доменная модель полезна не как “подготовительный этап перед архитектурой”, а как один из главных способов не спроектировать систему поверх ложной картины реальности.

В прошлой статье мы говорили об ubiquitous language и bounded contexts. Теперь посмотрим, что происходит, когда эти границы выбраны неверно — и как точная модель, наоборот, спасает архитектуру. Продолжим серию на примере лунной базы и разберём, как доменная модель может и ломать архитектуру, и удерживать её, если вовремя сохранить правильные различия в предметной области.

Одна база: неверная модель рушится, точная — держится

В статье

Почему ошибка в модели быстро становится ошибкой в архитектуре

Доменные ошибки редко остаются только в документации. Как только команда начинает строить систему, они быстро материализуются.

Если в модели:

  • слишком рано объединены разные сущности;
  • не различены важные состояния;
  • сглажены разные уровни критичности;
  • потеряны реальные границы процесса;

то это почти сразу начинает влиять на архитектурные решения.

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

  • запасы расходников;
  • энергетические резервы;
  • медицинские комплекты;
  • роботизированные платформы;
  • расходуемые материалы EVA-операций;
  • критические и некритические резервы.

То есть архитектура уже пошла от ложного обобщения.

Где доменная абстракция начинает врать

Слишком ранняя абстракция почти всегда кажется удобной. Она обещает:

  • единый словарь;
  • единый сервис;
  • единую модель данных;
  • более простую схему.

Но проблема в том, что такая абстракция часто не уменьшает сложность, а прячет её. Система начинает жить с “универсальными” понятиями, которые на деле не работают одинаково.

Типичные признаки того, что модель уже врёт:

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

Когда таких признаков становится много, проблема уже не в том, что “код разросся”. Проблема в том, что архитектура построена поверх неверного доменного обобщения.

Лунная база: опасность слишком общего понятия «ресурс»

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

На первый взгляд это удобно. Всё, чем база управляет:

  • вода;
  • кислород;
  • батареи;
  • запасные детали;
  • медицинские комплекты;
  • контейнеры с расходниками;
  • рабочее время роботов;

можно назвать “ресурсами” и сложить в одну модель учёта.

На диаграмме это даже выглядит элегантно. Но дальше начинает вскрываться разная природа этих объектов.

Чем они отличаются на самом деле

  • Вода и кислород — непрерывное потребление, критичность для выживания, жёсткие пороги, сценарии деградации.
  • Запасные детали — логистика, пополнение, складская модель, вероятностный запас под отказ.
  • Батареи и накопители — не просто “остаток”, а динамика заряда, генерации и приоритета потребителей.
  • Медицинские комплекты — режимы резервирования и ограниченный доступ.
  • Роботы — вообще не складской объект, а актив с состоянием, доступностью и операционным контекстом.

Формально всё это можно записать как:

  • resource_id
  • type
  • quantity
  • status

Но такая модель будет врать. Она создаст ложное ощущение единства там, где на самом деле предметная область требует разных правил, разных инвариантов и разных контекстов.

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

  • criticality — критичность (для кислорода жизненно, для запчастей нет);
  • reservation_mode — режим резервирования (аварийный резерв ≠ складской буфер);
  • owner_context — кто владеет объектом и правилами;
  • degradation_policy — что делать при деградации;
  • sync_priority — приоритет синхронизации с Землёй;
  • manual_override_required — нужно ли ручное подтверждение.

Каждый такой флаг — это сигнал, что внутри «ресурса» сидят разные сущности, которые модель пытается удержать заплатками вместо разделения.

Ложная модель против разделённой

Чтобы увидеть цену обобщения, вернёмся к сквозному сценарию серии: связь с Землёй пропала на 40 минут, а энергобюджет просел. Экипажу нужно решить, что отключить. И тут универсальный «ресурс» подводит первым: система не различает, что является аварийным резервом кислорода, а что — складским буфером запчастей; что нельзя трогать ни при каких условиях, а что можно отложить. Слишком общее понятие лишает систему именно той информации, которая нужна в критический момент.

Эта статья — про «ломает и спасает», поэтому удобнее всего сравнить напрямую:

Ложная модель Как ломает архитектуру Правильное разделение
Resource на всё универсальный API и мутный ownership условно: LifeSupportReserve (резерв жизнеобеспечения), EnergyBudget (энергобюджет), LogisticsStock (склад), RobotAsset (робот-актив)
единый status статусы без смысла, флаги и исключения разные state machines по контекстам
единое событие ResourceUpdated event-схемы с неясной семантикой доменные события по контекстам (OxygenLevelDropped, StockReplenished, RobotTaskCompleted)

Правый столбец — это не «больше сущностей ради красоты». Это разделение, при котором в сценарии просадки энергии система точно знает: LifeSupportReserve трогать нельзя, LogisticsStock можно отложить, а EnergyBudget — пересчитать приоритеты потребителей.

Как модель влияет на границы контекстов и ownership

Как только команда принимает неверную доменную модель, она почти автоматически начинает неверно делить систему.

Если “ресурс” воспринимается как единая сущность, дальше напрашивается:

  • один сервис;
  • одна схема данных;
  • единый owner;
  • единые API для операций с разными объектами.

Но как только система сталкивается с реальной жизнью, начинают появляться вопросы:

  • кто отвечает за критичные пороги жизнеобеспечения?
  • кто отвечает за энергетические решения?
  • кто определяет правила резервирования?
  • кто управляет роботизированными платформами?
  • кто владеет логикой supply chain, а кто — локальными операциями базы?

И тут выясняется, что единая модель породила искусственно связанную архитектуру. Ownership становится мутным, границы сервисов — спорными, а изменения в одном участке начинают зацеплять всё остальное.

Хорошая доменная модель в такой ситуации делает обратное:

  • помогает увидеть, где реально разные смыслы;
  • подсказывает естественные bounded contexts;
  • делает ownership объяснимым;
  • уменьшает количество “универсальных” API, которые потом становятся центром боли.

Как модель влияет на взаимодействия и консистентность

Неправильная доменная модель почти всегда ломает и взаимодействия между компонентами.

Если разные сущности свалены в одну модель, команда часто начинает ожидать и одинаковое поведение:

  • одинаковую консистентность;
  • одинаковый жизненный цикл;
  • одинаковую стратегию обновления;
  • одинаковую важность для observability и инцидентов.

Но в лунной базе это почти наверняка неверно.

Например:

  • данные о кислороде требуют одной модели приоритетов и реакции;
  • логистика запасных деталей — другой;
  • робототехнические задания — третьей;
  • аналитика поставок и пополнений — четвёртой.

Если перевести это в требования к консистентности, разница становится осязаемой:

  • кислород — near-real-time и локальная консистентность: решение нельзя откладывать и нельзя ставить в зависимость от связи с Землёй;
  • логистика запчастей — eventual consistency: можно согласовать «не сразу, но в итоге», задержка не критична;
  • наземная аналитика — batch/lagging (пакетная и запаздывающая обработка): данные могут отставать, это нормально для прогноза и отчётности.

Одна универсальная модель данных не может одновременно быть и near-real-time, и batch — поэтому попытка свести всё к «ресурсу» рано или поздно ломает либо кислород, либо аналитику.

Если это не развести на уровне модели, архитектура позже попытается компенсировать это:

  • дополнительными статусами;
  • event-схемами с неясной семантикой;
  • reconciliation-процессами;
  • ручными исключениями;
  • “особым поведением” в коде.

То есть систему начинает спасать не архитектурная ясность, а накопление специальных случаев.

flowchart LR A["Смазанное доменное понятие"] --> B["Неверная граница контекста"] B --> C["Искусственно связанная архитектура"] C --> D["Сложные API и ownership"] D --> E["Трудная эволюция и интеграции"] F["Точная доменная модель"] --> G["Естественные контексты"] G --> H["Объяснимая декомпозиция"] H --> I["Предсказуемые взаимодействия"] I --> J["Более устойчивая архитектура"]

flowchart LR
  A["Смазанное доменное понятие"] --> B["Неверная граница контекста"]
  B --> C["Искусственно связанная архитектура"]
  C --> D["Сложные API и ownership"]
  D --> E["Трудная эволюция и интеграции"]

  F["Точная доменная модель"] --> G["Естественные контексты"]
  G --> H["Объяснимая декомпозиция"]
  H --> I["Предсказуемые взаимодействия"]
  I --> J["Более устойчивая архитектура"]
Ошибка в доменной модели быстро переходит в архитектурную ошибку

Как хорошая модель спасает систему от ложной сложности

Хорошая доменная модель не обязана сделать систему простой. Иногда предметная область сама по себе сложна. Но она помогает сделать сложность честной.

Это значит:

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

В кейсе лунной базы это может привести, например, к более точному разделению на:

  • контур жизнеобеспечения;
  • контур энергетики;
  • контур логистики и запасов;
  • контур роботизированных операций;
  • внешний контур наземного планирования и аналитики.

Тогда архитектура начинает рождаться не из желания “минимизировать количество сервисов” или “сделать всё единообразно”, а из понимания того, где различие действительно важно для системы.

Короткий checklist перед архитектурной декомпозицией

Перед тем как делить систему на сервисы, модули или контуры, полезно проверить:

  1. Какие сущности в модели сейчас слишком обобщены?
  2. Где одно и то же слово скрывает разные доменные смыслы?
  3. Какие инварианты и состояния различаются сильнее, чем кажется в документации?
  4. Где “единая модель” удобна только на схеме, но не в жизни системы?
  5. Какие контексты должны иметь разный ownership?
  6. Где различия в домене неизбежно повлияют на взаимодействия и консистентность?
  7. Что команда сейчас объединяет просто потому, что так легче начать?

Если эти вопросы не заданы, архитектура очень легко закрепит красивое, но ложное упрощение.

Что дальше

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

См. также

Обсуждение в Telegram

Присоединиться →

Комментарии