Мы рассмотрим доменные модели, которые лежат в основе системной разработки в стиле DDD. Доменные модели закладывают недвусмысленный, строгий фундамент описания функций системы.
При моделировании и реализации моделей в коде полезно иметь под рукой ка- кие-нибудь составные элементы. Доменные модели обычно основаны на объектах- значениях и сущностях. Более крупные структуры, как правило, представлены в виде агрегатов. Использование этих элементов сделает ваш код более строгим и менее склонным к появлению уязвимостей.
В системной разработке слово «модель» может означать много разных вещей: диа- граммы потоков в UML, способ хранения информации в таблицах базы данных и т. д. В DDD модель описывает основные бизнес-аспекты, с которыми вы имеете дело, в виде определенного набора концепций. Зачем нам нужны такие модели и как они должны выглядеть?
Вам нужны доменные модели, которые способствуют стабильной и безопасной разработке. Эффективная модель должна:
* быть простой, чтобы вы могли сосредоточиться на ключевых моментах;
* быть строгой, чтобы служить основой для написания кода;
* обеспечивать глубокое понимание, чтобы сделать систему по-настоящему по- лезной;
* быть лучшим выбором с прагматической точки зрения;
* предоставлять язык, который можно использовать при обсуждении системы.
Модель — это упрощенная версия реальности, из которой убрано все то, что вам не нужно. Модель — это не диаграмма, а определенный набор абстракций.
### Модели должны быть строгими
Доменная модель — это не просто упрощенная версия реальности: недостаток де- талей компенсируется дополнительной строгостью. Чтобы определить, какими деталями можно пожертвовать в угоду точности, нужно приложить много усилий. И если вы хотите сделать это как следует, придется обратиться к людям с глубоким пониманием предметной области.
Несколько терминов:
• предметная область (домен) — та часть реального мира, в которой что-то проис- ходит (например, область управления багажом);
• доменная модель — отфильтрованная версия предметной области, в которой каждая концепция имеет определенное значение;
• код — закодированная версия доменной модели, написанная на языке програм- мирования.
### Модели вбирают в себя глубокое понимание предметной области
Лучшие модели получаются при взаимодействии разработчиков и специа- листов в предметной области — это постепенный и многошаговый процесс.
### Модель не создают, а выбирают
Создание модели подразумевает сознательный вы- бор из множества вариантов: модель должна соответствовать вашим потребностям, которые определяют ее назначение.
В процессе моделирования старайтесь подобрать разные модели, которые вы- ражают вашу предметную область. Попробуйте найти три модели и сравните, насколько хорошо они подходят для ваших задач. Поиск лучшего варианта имеет большое значение, так как это позволяет рассуждать о предметной области эффек- тивно и недвусмысленно. Из хорошей модели получается язык.
### Модель формирует единый язык
Интересным аспектом моделирования является то, что оно формирует язык, на котором мы рассуждаем о системе. Для начала необходимо отметить, что, когда специалисты в предметной области общаются друг с другом, они говорят на своем собственном языке. Это язык предметной области (или доменный язык).
Задумайтесь на минуту о том, на каком языке разговаривают системные разра- ботчики. Общаясь друг с другом, мы можем легко бросаться терминами, которые для нас звучат логично, но будут совершенно непонятными людям, работающим в других областях: например, мы можем создать «пул соединений» или сделать из чего-то «стратегию». Специалисты в финансовой области, логистике или здравоохранении тоже имеют свой жаргон.
Если вы разрабатываете логистическую систему, кажется логичным взять терми- нологию из логистики и закодировать ее в виде программной системы. Это прекрас- ная, но, к сожалению, плохая идея. Язык, который используют специалисты в этой области, не является логически последовательным. И вовсе не из-за того, что они сознательно небрежно обращаются со своей терминологией.
## Составные элементы модели
тобы отобразить свою модель в коде, вам понадобится набор составных элементов. Они должны быть четко определены, так как нужны для упорядочения и структури- рования сложных моделей. Это фреймворк, который позволяет вам четко отделить логику предметной области от остального кода и помогает справляться с техниче- скими аспектами процесса.
В DDD составными элементами, которые представляют для нас особый интерес, являются сущности, объекты-значения и агрегаты
![Структура данных буферного кэша](https://whoisdeveloper.ru/static/img/ddd1.png)
### Сущности
Каждая часть доменной модели обладает определенными характеристиками и зна- чением. Сущности — это элементы модели, имеющие отчетливые свойства. Вот что делает их особенными.
* У сущности есть идентификатор, который ее определяет и отличает от других сущностей.
* Этот идентификатор не меняется на протяжении всего жизненного цикла сущ- ности.
* Сущность содержит другие объекты, в том числе другие сущности или объекты- значения.
* Сущность отвечает за координацию операций с принадлежащими ей объек- тами.
Это означает следующее: чтобы узнать, совпадают ли две сущности, нужно про- верять их идентификаторы, а не атрибуты. Именно идентификатор определяет сущность, какими бы ни были ее атрибуты. Идентификатор никогда не меняется. На протяжении своего жизненного цикла сущность может видоизменяться и при- обретать много разных атрибутов и аспектов поведения, но ее идентификатор всегда остается прежним.
#### Непрерывность идентификатора
Иногда доменный объект определяется своими атрибутами, но бывает, что они со временем меняются, не влияя на идентификатор объекта. Например, представление клиента можно определить с помощью его атрибутов: имени, возраста и адреса. Большинство из них могут измениться на протяжении существования клиента в системе, но это по-прежнему тот же клиент с той же историей заказов, поэтому его идентификатор должен оставаться неизменным.
Клиент определяется не своими атрибутами, а скорее своей личностью (иденти- фикатором), поэтому его следует моделировать в виде сущности. Таким образом, пока клиент существует в системе, его идентификатор остается согласованным независимо от того, сколько раз на протяжении этого времени менялось его состояние.
На практике предпочтение следует отдавать сгенерированным ID, а не иден- тификаторам на основе атрибутов.
#### Локальная, глобальная и внешняя уникальность
Идентификатор объекта имеет большое значение, но он может быть уникальным на разных уровнях. Возьмем, к примеру, сущность «клиент». Система могла бы ис- пользовать идентификатор, уникальный не только внутри нее, но и снаружи. Такой идентификатор называется внешне уникальным. Примером могут служить удосто- верения личности или номера, которые во многих странах применяются для иден- тификации граждан. В Соединенных Штатах это номер социального страхования. Однако использование идентификатора, определенного вне системы, может иметь определенные недостатки, которые, как вы увидите в следующих главах, способны угрожать безопасности.
Наверное, более распространенными являются идентификаторы, уникальность которых ограничена рамками системы или текущей модели. Их можно называть глобально уникальными.
Иногда одни сущности находятся внутри других. Поскольку такими инкапсу- лированными сущностями управляет их владелец, их идентификаторы могут быть уникальными только в пределах этой сущности, и этого обычно достаточно. Такие идентификаторы называют локальными на уровне владеющей сущности
Вернемся к нашему примеру и представим, что система управляет клиентами в роз- ничных магазинах и каждый клиент относится к одному и только одному магазину. В таком случае идентификатор достаточно сделать уникальным на уровне магази- на, к которому относится клиент. Моделирование локальной уникальности может упростить функцию генерации ID. К тому же это позволяет явно переложить всю ответственность за управление данными клиентами на сущность, в которой они инкапсулированы.
#### Сущности должны быть узкоспециализированными
При моделировании сущности старайтесь добавлять только те атрибуты и аспекты поведения, которые важны для ее определения или помогают ее идентифицировать. Все остальное должно быть вынесено из сущности в другие объекты модели, которые затем можно сделать ее частью. Это могут быть как другие сущности, так и объекты- значения.
Сущности являются главным механизмом представления концепций в домен- ной модели, однако не все аспекты модели определяются их идентификаторами. Для определения некоторых концепций применяются их значения. Для моделиро- вания таких концепций используются объекты-значения.
#### Объекты-значения
Ключевые характеристики таких объектов перечислены далее:
* определяются не идентификатором, а своим значением;
* неизменяемые;
* должны составлять целостную концепцию;
* могут ссылаться на сущности;
* явно определяют и обеспечивают соблюдение важных ограничений;
* могут использоваться в качестве атрибутов в сущностях и других объектах-зна-
чениях;
* могут иметь короткое время жизни.
1. Определяются своим значением
Объект-значение определяется своим значением, а не идентификатором, поэтому два объекта одного типа считаются равными, если их значения совпадают. Нас ин- тересует только то, что они собой представляют, но не кем именно они являются1. У объектов-значений нет идентификаторов. Это полная противоположность тому, как мы определяем сущности.
2. Неизменяемые
Поскольку объекты этого вида определяются своими значениями, необходимо по- заботиться о том, чтобы значения не менялись. В противном случае это будут уже другие объекты. По этой причине объекты-значения должны быть неизменяемыми. Если бы их можно было изменять, это могло бы нарушить инварианты других объ- ектов, внутри которых они находятся. Неизменяемость позволяет также передавать объекты в качестве аргументов и делает возможными различные технические оп- тимизации, такие как повторное использование объектов в средах с ограниченной памятью и упрощение работы с ними в многопоточных системах.
3. Целостная концепция
Объект-значение может состоять из одного или нескольких атрибутов либо других аналогичных объектов. Он также может ссылаться на сущности, но не может их содержать. Причина этого в том, что значение сущности может меняться. Если бы объект-значение не ссылался на сущность, а содержал ее, то любое ее изменение меняло бы сам объект. А это нарушило бы его неизменяемость.
4. Определяют и соблюдают важные ограничения
Представьте, что у вас есть объект-значение Age с одним целым числом внутри. В Java, к примеру, целое число по умолчанию находится в диапазоне от –231 до 231 – 1. Вряд ли такой диапазон можно назвать типичным для чело- веческого возраста. Поэтому, чтобы прояснить определение возраста, его следует смоделировать в виде объекта-значения с подходящими ограничениями или инва- риантами, как показано на рисунке. В ходе моделирования вы можете прийти к выводу о том, что возраст человека должен находиться в пределах между 0 и 150 годами. Возможно, ваша предметная область не допускает детей, поэтому минимальный возраст может быть 18 лет.
#### Агрегаты
Когда вы имеете дело с объектом модели с жизненным циклом, таким как сущность, необходимо убедиться в том, что его состояние остается корректным на протяжении всего срока жизни. Для этого, возможно, придется реализовать довольно много логи- ки и использовать код для работы с механизмами блокирования, чтобы обеспечить поддержку конкурентных операций и запись изменений в постоянное хранилище. Независимо от того, сохраняется сущность или нет, можно сказать, что изменение состояния происходит в рамках транзакции.
Агрегат — это концептуальная граница, с помощью которой можно сгруппиро- вать отдельные части модели. Это делается для того, чтобы во время изменения состояния с агрегатом можно было работать как с единым целым, это граница, внутри которой должны производиться транзакции. Граница, определяемая агре- гатом, выбирается не произвольно или технически. Она тщательно формируется на основе глубокого понимания модели.
При моделировании агрегата необходимо следовать строгим правилам, чтобы он работал как следует и выполнял поставленную перед ним задачу. Далее перечислены правила:
У каждого агрегата есть граница и корень.
1. Роль корня играет одна конкретная сущность, размещенная внутри агрегата.
2. Корень является единственным членом агрегата, на который могут ссылаться объекты за пределами границы. Поэтому:
* у корня есть глобальный идентификатор;
* корень управляет доступом к объектам внутри границы;
* все сущности, кроме корня, имеют локальные идентификаторы, которые могут быть неизвестны за пределами агрегата;
* корень может передавать другим объектам ссылки на внутренние сущности, но эти ссылки могут использоваться лишь временно, и их нельзя удерживать;
* корень может передавать другим объектам ссылки на объекты-значения.
4. Инварианты между членами агрегата всегда соблюдаются в рамках каждой
транзакции.
5. Инварианты, охватывающие несколько агрегатов, не дают гарантии согласован- ности в любой момент, но в конечном счете они могут стать согласованными.
6. Объекты внутри агрегата могут содержать ссылки на другие агрегаты.
Агрегат — это концептуальная граница, и он содержит сущность, которая яв- ляется его корнем. В целом на этапе реализации корневая сущность и сам агрегат являются одним и тем же объектом. Вам, возможно, будет легче о них рассуждать, если вы будете считать их идентичными.
Корень агрегата — это единственная его часть, которая видна вне его границ. Корень управляет доступом ко всему, что находится внутри агрегата. Это делает его идеальным местом для размещения всех инвариантов, охватывающих разные объекты внутри границ. Его нельзя обойти при условии, что вы соблюдаете правила моделирования агрегатов. Это также означает, что корень является единственным элементом, к которому можно обращаться через репозиторий(технологически независимыми хранилищами для корней агрегатов). Это еще один способ управления доступом к агрегату, который гарантирует, что сущности, находящиеся внутри него, не могут быть напрямую модифицированы внешними объектами.
## Ограниченные контексты
#### Отношения между языком, моделью и ограниченным контекстом
Отношения между языком, моделью и ограниченным контекстом становятся оче- видными, если смотреть на них с точки зрения семантики. Модель — это абстракция предметной области, в которой находятся концепции, отношения и термины единого языка. Это тесно связывает язык и модель между собой — и не только за счет тер- минов и отношений, но и на уровне семантики: концепция, принадлежащая модели, должна иметь то же значение, что и в языке, и наоборот.
## Взаимодействие между контекстами
Граница контекста представляет интерес с точки зрения безопасности. Давайте подумаем, как происходит взаимодействие между контекстами. Когда данные пере- секают границу, они автоматически принимают семантику единого языка и модели другого контекста. Из этого следует, что бездействие в этом процессе создает риск появления уязвимостей. И хотя это может показаться очевидным, но проблемы такого рода встречаются на удивление часто.
### Создание карты контекстов
Карта контекстов — это концептуальное представление того, как взаимодейству- ют разные контексты. Это может быть диаграмма, текстовое описание или просто взаимопонимание между командами. В любом случае ключевой ее особенностью является то, что она помогает определить концепции, которые пересекают семан- тические границы.
Для начала будет полезно нарисовать простую общую схему взаимодействий внутри системы при обработке заказа
![Структура данных буферного кэша](https://whoisdeveloper.ru/static/img/ddd2.png)
Вот как взаимодействуют отделы финансов и доставки.
1. Отдел финансов получает новый заказ.
2. Общая сумма заказа резервируется на счете, который указан в платежной инфор- мации.
3. Отдел финансов передает заказ на обработку в отдел доставки.
4. Отдел доставки обрабатывает и отправляет заказ.
5. Отдел доставки уведомляет отдел финансов о новом статусе заказа.
6. Отдел финансов завершает денежную трансакцию.
Эту блок-схему легко превратить в карту контекстов, из которой становится ясно,
что контекст доставки вторичен по отношению к контексту финансов. Это может казаться очевидным, но само понимание того, что финансовый заказ не- обходимо преобразовать в заказ отдела доставки, имеет огромное значение. Данное отношение явно указывает на необходимость четких связей и на то, что для успеш- ной работы команды должны взаимодействовать.
![Структура данных буферного кэша](https://whoisdeveloper.ru/static/img/ddd3.png)