# Оглавление 1. [**Предпосылки к проектированию ПО**](#01) 2. [**Процесс проектирования ПО**](#02) 3. [**Архитектура. Архитектурные шаблоны**](#03) 4. [**Модель предметной области. Предметно-ориентированное проектирование**](#04) 5. [**Переработка знаний**](#05) 6. [**Коммуникация и язык**](#06) 7. [**Связь между моделью и реализацией**](#07) 8. [**Структурные элементы предметной-ориентированного проектирования**](#08) 9. [**Модель, выраженная в программе**](#09) 10. [**Цикл существования объектов модели**](#10) 12. [**Стратегическое проектирование**](#11) 13. [**Поддержание целостности модели**](#12) 14. [**Дистилляция**](#13) 15. [**Крупномасштабная структура**](#14) ## 01. Предпосылки к проектированию ПО **Проектирование программного обеспечения** — процесс создания проекта программного обеспечения (ПО), а также дисциплина, изучающая методы проектирования. Проектирование ПО является частным случаем проектирования продуктов и процессов. #### Эволючия проектирования ПО - **50-е годы:** Программирование велось, в основном, в машинном коде (Решались научно-технические задачи `счет по формулам`). При разработке ПО почти сразу приступали к составлению программы по заданию. Если задание изменялось, то время составления программы сильно увеличивалось, минимальная документация оформлялась уже после того, как программа начинала работать. **Сложность программ в машинных кодах ограничивалась способностью программиста одновременно отслеживать последовательность выполняемых операций и местонахождение данных при программировании.** ![image](https://gist.github.com/assets/103507130/91f329c0-958f-49ab-99a4-0b763a99c301) - **60-е годы:** **Характеризуются** развитием и широким **использованием языков программирования высокого уровня** (АЛГОЛ 60, ФОРТРАН и т.д.). Революционным было появление в языках программирования средств, позволяющих оперировать подпрограммами. Подпрограммы можно было сохранять и использовать в других программах. Типичная программа того времени состояла из основной программы, области глобальных данных и набора подпрограмм (в основном библиотечных), выполняющих обработку всех данных или их части. **Слабым местом такой архитектуры было то, что при увеличении количества подпрограмм возрастала вероятность искажения части глобальных данных какойлибо подпрограммой.** Чтобы сократить количество таких ошибок в подпрограммах, было предложено размещать локальные данные. - Было: ![image](https://gist.github.com/assets/103507130/1cdbd8b8-aa8a-4ade-abf3-c12e709c0cec) - Стало: ![image](https://gist.github.com/assets/103507130/c5d2b7e1-77b7-4b18-9184-848785c2bdd8) `Сложность разрабатываемого ПО` при использовании `подпрограмм с локальными данными` по-прежнему ограничивалась возможностью программиста отслеживать процессы обработки данных, но уже на новом уровне. **Появление компьютеров 2-го поколения привело к развитию мультипрограммирования и созданию относительно больших программных систем.** `В этот период стало понято, что важно не только то, на каком языке программируют, но и то, как программируют.` - **70-е годы:** Получили широкое распространение информационные системы и базы данных, так как стоимость хранения информации на компьютерных носителях стала меньше, чем на традиционных. Началось интенсивное развитие технологий программирования в следующих направлениях: 1. Обоснование и широкое внедрение нисходящей разработки и структурного программирования. 2. Развитие абстрактных типов данных и модульного программирования (в частности, возникновение идеи разделения спецификации и реализации модулей и использование модулей, скрывающих структуры данных); 3. Создание методики для управления коллективной разработкой программных средств. 4. Исследование проблем обеспечения надежности и мобильности программных средств. 5. Появление инструментальных программных средств (программных инструментов) поддержки технологии программирования. `Структурный подход к программированию` - это совокупность методов - технологических приемов, охватывающих выполнение всех этапов разработки программного обеспечения. **В основе структурного подхода лежит декомпозиция (разбиение на части) сложных систем с целью последующей реализации в виде отдельных небольших подпрограмм**. `С появлением других принципов декомпозиции (объектного, логического и т. д.) данный способ получил название процедурной декомпозиции.` В отличие от используемого ранее процедурного подхода к декомпозиции, структурный подход требовал представления задачи в виде иерархии подзадач простейшей структуры. Проектирование, осуществлялось `"сверху - вниз"` и подразумевало реализацию общей идеи, обеспечивая проработку интерфейсов подпрограмм. **Модульное программирование предполагает выделение групп подпрограмм, использующих одни и те же глобальные данные в отдельно компилируемые модули (библиотеки подпрограмм)**, например модуль графических ресурсов, модуль подпрограмм вывода на принтер. - Связи между модулями при использовании данной технологии осуществляются через специальный интерфейс, в то время как доступ к реализации модуля (телам подпрограмм и некоторым "внутренним" переменным) запрещен. Эту технологию поддерживают современные версии языков Pascal и С (C++) к примеру. `Модульное программирование` - существенно упростило разработку ПО и повысило производительность труда программистов, так как благодаря ему каждый из программистов мог разрабатывать свои модули независимо, обеспечивая взаимодействие модулей через специально оговоренные межмодульные интерфейсы, а также в последствии переиспользовать разработанные модули в других разработках. ![image](https://gist.github.com/assets/103507130/61b65719-fee8-4016-99b6-47e7405fa3d5) Только есть одно **НО**! Практика показала, что структурный подход в сочетании с модульным программированием позволяет получать достаточно надежные программы, но все же узким местом модульного программирования является то, что ошибка в интерфейсе при вызове подпрограммы выявляется только при выполнении программы (из-за раздельной компиляции модулей обнаружить эти ошибки раньше невозможно). При увеличении размера программы обычно возрастает сложность межмодульных интерфейсов, и с некоторого момента предусмотреть взаимовлияние отдельных частей программы становится практически невозможно. > Для разработки ПО большого объема было предложено использовать `объектный подход`. - **80-е годы:** Развитие пользовательских интерфейсов и созданию четкой концепции качества программных средств. Широко используется объектный подход, создаются различные инструментальные среды разработки и сопровождения ПО. Развивается концепция компьютерных сетей. Объектно-ориентированное программирование - подход, технология для проектирования и создания сложного ПО, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного типа (класса), а классы образуют иерархию с наследованием свойств. #### **Взаимодействие программных объектов в такой системе осуществляется путем передачи сообщений.** ![image](https://gist.github.com/assets/103507130/68dae091-9f28-41d4-9c4d-aba62df5b8ee) Основным `достоинством объектно-ориентированного программирования` по сравнению со структурным является "более естественная" декомпозиция программного обеспечения, которая существенно облегчает его разработку. Это приводит к более полной локализации данных и интегрированию их с подпрограммами обработки, что позволяет вести практически независимую разработку отдельных частей (объектов) программы. `Объектный подход` предлагает новые способы организации программ, основанные на механизмах **наследования, полиморфизма, композиции, наполнения**. Эти механизмы позволяют конструировать из сравнительно простых объектов сложные. В результате существенно увеличивается показатель повторного использования кодов и появляется возможность создания библиотек классов для различных применений. - **90-е годы:** Широкий охват компьютеров международной компьютерной сетью, из-за чего появилось множество проблем - регулирования доступа к информации компьютерных сетей. Все стали глубоко озабочены, как защититить данные и передаваемые по сети сообщения. #### В связи с этим появились новые направления разработки ПО 1. `Компонентный подход` - построение ПО из физически отдельно существующих `компонентов` (частей ПО), которые взаимодействуют между собой через стандартизованные двоичные интерфейсы. В отличие от обычных объектов - объекты-компоненты можно собрать в динамически вызываемые библиотеки или исполняемые файлы, распространять в двоичном виде (без исходных текстов) и использовать в любом языке программирования, поддерживающем соответствующую технологию. ![image](https://gist.github.com/assets/103507130/21cbc480-42f0-4079-9ff1-acf07f69d8d4) ## 02. Процесс проектирования ПО `Цель проектирования ПО` - определить и детализировать внутренние свойства системы и внешние (**видимые**), исходя из требований (исходных условий задачи) заказчика к ПО. Все требования заказчика подвергаются анализу. #### Основные этапы при проектировании ПО: - выбор метода и стратегии решения - выбор представления внутренних данных - разработка основного алгоритма - документирование ПО - тестирование и подбор тестов - выбор представления входных данных Ход процесса проектирования и его результаты зависят не только от состава требований, но и выбранной модели процесса **Модель предметной области накладывает ограничения на бизнес-логику и структуры данных.** ----- В процессе проектирования ПО для выражения его характеристик используются различные нотации — блок-схемы, ER-диаграммы, UML-диаграммы, DFD-диаграммы, а также макеты. - Проектированию обычно подлежат: - Архитектура ПО - Устройство компонентов ПО - Пользовательские интерфейсы ## 03. Архитектура `Архитектура программного обеспечения` — это структура и организация компонентов программы, определяющая их взаимодействие и отношения для обеспечения эффективной работы и удовлетворения требований проекта. #### Архитектурный вид состоит из 2 компонентов: - Элементы - Отношения между элементами #### Архитектурные виды можно поделить на 3 основных типа: - `Модульные виды` (англ. module views) — показывают систему как структуру из различных программных блоков - `Компоненты-и-коннекторы` (англ. component-and-connector views) — показывают систему как структуру из параллельно запущенных элементов (компонентов) и способов их взаимодействия (коннекторов). - `Размещение` (англ. allocation views) — показывает размещение элементов системы во внешних средах. ----- **Примеры `модульных` видов:** - `Декомпозиция` (англ. decomposition view) — состоит из модулей в контексте отношения «является подмодулем» - `Использование` (англ. uses view) — состоит из модулей в контексте отношения «использует» (т.е. один модуль использует сервисы другого модуля) - `Вид уровней` (англ. layered view) — показывает структуру, в которой связанные по функциональности модули объединены в группы (уровни) - `Вид классов/обобщений` (англ. class/generalization view) — состоит из классов, связанные через отношения «наследуется от» и «является экземпляром» ----- **Примеры видов `компонентов-и-коннекторов`:** - `Процессный вид` (англ. process view) — состоит из процессов, соединённых операциями коммуникации, синхронизации и/или исключения. - `Параллельный вид` (англ. concurrency view) — состоит из компонентов и коннекторов, где коннекторы представляют собой «логические потоки». - `Вид обмена данными` (англ. shared-data (repository) view) — состоит из компонентов и коннекторов, которые создают, сохраняют и получают постоянные данные. - `Вид клиент-сервер` (англ. client-server view) — состоит из взаимодействующих клиентов и серверов, а также коннекторов между ними (например, протоколов и общих сообщений). ----- **Примеры видов `размещения`:** - `Развертывание` (англ. deployment view) — состоит из программных элементов, их размещения на физических носителях и коммуникационных элементов. - `Внедрение` (англ. implementation view) — состоит из программных элементов и их соответствия файловым структурам в различных средах (разработческой, интеграционной и т.д.) - `Распределение работы` (англ. work assignment view) — состоит из модулей и описания того, кто ответственен за внедрение каждого из них ---------------------------------------------------------------- #### Архитектурные шаблоны Для удовлетворения проектируемой системы различным атрибутам качества применяются различные архитектурные шаблоны (паттерны). Каждый шаблон имеет свои задачи и свои недостатки. #### Примеры архитектурных шаблонов: 1. `Многоуровневый шаблон` (Layered pattern) ![image](https://gist.github.com/assets/103507130/ac225b3f-3997-4256-a42a-11bd1b83cfa1) **Суть:** система разбивается на уровни, которые на диаграмме изображаются один над другим. Каждый уровень может вызывать только уровень на 1 ниже него. **Плюсы:** разработку каждого уровня можно вести относительно независимо, что повышает модифицируемость системы. **Минусы:** усложнение системы и снижение производительности. 2. `Шаблон посредника` (Broker pattern) ![image](https://gist.github.com/assets/103507130/ddc653b6-a142-46bf-bb2d-660015c2467d) **Суть:** зачастую в системах присутствует большое количество модулей, их прямое взаимодействие друг с другом становится слишком сложным. Для решения проблемы вводится посредник (например, шина данных), по которой модули общаются друг с другом. **Плюсы:** повышение функциональной совместимости модулей системы. **Минусы:** из-за наличия посредника понижается производительность, так как в случае технических сбоев, его недоступность может сделать недоступной всю систему, а также он может стать объектом атак и узким местом системы. 3. `Шаблон «Модель-Представление-Контроллер» `(Model-View-Controller pattern) ![image](https://gist.github.com/assets/103507130/acfa80fe-6ad9-4ba0-8237-a67711aa23bc) **Суть:** Т.к. требования к пользовательскому интерфейсу меняются чаще всего, то возникает потребность часто его модифицировать, при этом сохраняя корректное взаимодействие с данными (чтение, сохранение). Для этого интерфейс отделяется от данных, обеспечивая возможность менять интерфейсы, равно как и создавать их разные варианты. - **MVC система разделена на:** - Модель, хранящую данные - Представление, отображающее часть данных и взаимодействующее с пользователем - Контроллер, являющийся посредником между видами и моделью **Плюсы:** отделение бизнес-логики от пользовательского интерфейса и работы с базой данных, что благоприятно сказывается на поддержке и обслуживании. **Минусы:** из-за усложнения взаимодействия падает скорость работы системы. ----- Разновидности шаблона MVC: - MVP (Model-View-Presenter) - MVVM (Model-View-ViewModel) ------ 4. `Клиент-серверный шаблон` (Client-Server pattern) ![image](https://gist.github.com/assets/103507130/0d4fcae7-eeb0-4c11-a77b-ce634c20f9ad) ![image](https://gist.github.com/assets/103507130/031635b3-809e-48b8-9d1a-670e0c76d5dc) **Суть:** Есть общие ресурсы и сервисы, к которым нужно обеспечить доступ большого количества распределенных клиентов, и при этом необходимо контролировать доступ или качество обслуживания. `Клиент-серверный шаблон` даёт возможность построения распределённых систем, в которых сервер централизует ресурсы и рабочую нагрузку в одном месте, позволяя множеству потребителей использовать эти данные независимо. **Плюсы:**повышает масштабируемость и доступность системы. **Минусы:** сервер может стать узким местом системы, при его недоступности становится недоступна вся система. ## 04. Модель предметной области Для успешного проектирования программы важно изучать предметную область бизнес-задачи и отражать её в архитектурном проекте приложения. > **2 идеи Эрика Эванса для успешного проектирования ПО:** > > 1. Сосредотачивайтесь на логической структуре предметной области и ее взаимосвязях. > 2. Стройте архитектуру сложного ПО на основе модели предметной области. `Предметно-ориентированное проектирование` (Domain-Driven Design) - подход к проектированию и разработки ПО, основное внимание которого уделяется пониманию и моделированию предметной области бизнеса. DDD обеспечивает тесное сотрудничество клиента и разработчиков. Заказчик посвящает команду в бизнес-логику своей компании, объясняет, как устроена ее работа и тд. Такое взаимодействие позволяет разработчикам глубже понимать бизнес-потребности, чтобы отразить это на архитектурном проекте приложения. > **Основной принцип DDD — разделение программного продукта на домены.** - `Домен` — предметная область, которая описывает совокупность проблем и целей бизнеса. - `Домены` в свою очередь делятся на `субдомены` — подобласти, которые отвечают за отдельные проблемы. **Пример `субдомена`:** для сервиса грузоперевозок в качестве `субдомена` оформление заказа и выбор оптимального маршрута ----- Domain Driven Design использует слоистую архитектуру. Её главным компонентом является смысловое ядро — **Core domain** — часть домена, имеющая первостепенное значение для выполнения главной задачи. Именно в этом ядре концентрируются знания о предметной области, поверх которых потом формируются новые слои со служебной и второстепенной функциональностью. Создавая модель предметной области, разработчик не только проектирует архитектуру под текущие бизнес-процессы, но и создает возможности для последующего расширения. Если это будет возможно в предметной области, это будет возможно и в модели этой предметной области, а значит и в ПО. - `основной домен` —> `core domain` - это ядро продукта, его основная функция. **Пример `core domain`:** если продолжить пример с перевозкой грузов: основная задача приложения — обеспечить доставку посылки из пункта А в пункт Б. ----- Принципы DDD в целом ориентированы на семейство гибких методологий. В частности, всегда подразумевается использование в проектах двух подходов: - Итеративный подход к разработке. (**выполнение работ параллельно с непрерывным анализом полученных результатов и корректировкой последующих этапов работы**) - Тесное взаимодействие между разработчиками и специалистами в предметной области. ----- `Модель` – это упрощение; эспециально отобранный и упрощенный запас знаний в структурированной форме. **(Т. е. выделяются только важные аспекты для решения задачи, лишнее игнорируется)** `Модель предметной области` - это строго организованная выборка из знаний специалистов области. ----- Выбор модели в предметно-ориентированном проектировании определяется способами ее использования при разработке программы: 1. Модель и архитектура программы взаимно определяют друг друга. 2. Модель лежит в основе языка, на котором говорят все члены группы разработчиков. 3. Модель это дистиллированное знание. ## 05. Переработка знаний ![image](https://gist.github.com/assets/103507130/0b61adef-6d72-477a-810c-1cc11272d924) `Переработка знаний` — **получение информации о предметной области в понятной и структурированной форме**, отделяя при этом все лишнее. - **Основная цель `переработки знаний`** - найти простое представление информации, которое бы придало смысл всему массиву данных. - **Результат:** система абстрактных понятий, учитывающая все необходимые подробности для решения задачи. #### Составляющие эффективного моделирования 1. Установить связь между моделью и ее реализацией. 2. Ввести в обиход язык, основанный на модели. 3. Разработать информоемкую модель. 4. Заниматься дистилляцией модели. 5. Экспериментировать и проводить мозговые штурмы. #### Модель не просто схема хранения данных, а целостная методика решения сложных задач. Она должна содержать в себе и обобщать обилие информации разного рода. Именно благодаря следованием данной методики, даже в самом грубом прототипе присутствовала существенная связь с действительностью, и после всех последующих доработок эта связь сохранится. В процессе создания модели в нее добавляются важные понятия, однако, если бесполезное понятие тесно связано с необходимым, то находится новая модель, в которой существенное понятие легко отделяется и можно легко отбросить бесполезное. - Благодаря совместной работе разработчиков и специалистов предметной области, `программисты` осваивают фундаментальные принципы той области деятельности, для которой они пишут, а `специалисты` приходят к пониманию того, какая строгость понятий нужна для реализации программного проекта. - Это превращает группу разработчиков в квалифицированных `«переработчиков знаний»` (knowledge crunchers) - `Модель` тесно связана с процессом программирования и архитектурного проектирования. - Совершенства модели никогда не достигают, но они эволюционируют ----- Если по какой-то причине прекращается устная передача знаний в группе, это означает их потерю. ## 06. Коммуникация и язык `Единый язык` служит основным средством для выражения аспектов архитектуры, которые не отражаются в коде, таких как крупномасштабные структуры, контексты, определяющие взаимосвязи между системами, и различные шаблоны, связанные с моделью и программной архитектурой. > Отсутствие единого языка в проекте приводит к серьезным проблемам. > > Специалисты в предметной области используют свой жаргон, в то время > как разработчики применяют собственный язык, ориентированный на > программную архитектуру В качестве основы общего языка для коммуникации в рамках проекта по разработке ПО может служить `Модель предметной области`. `Модель предметной области` обеспечивает **единый "язык" понимания**, который помогает избежать недопониманий и неоднозначностей во время обсуждений. Это средство коммуникации становится ключевым элементом при создании текстовой документации, разработке неформальных диаграмм и ведении дискуссий в рамках гибких методологий. - Этот язык включает имена классов, основные операции и термины, связанные с правилами модели. Также в него входят слова для описания высокоуровневых принципов и шаблонов, применяемых к модели. - Изменения в языке следует принимать как изменения в модели (**Разработчики должны вносить изменения в диаграммы классов, переименовывать классы и методы в исходном коде, а иногда даже изменять функции программы, чтобы соответствовать изменениям в терминах проекта**) - Единый язык превращает модель из дополнения к архитектуре в естественную среду для совместных действий программистов и специалистов. #### Один из эффективных методов улучшения модели — `описывать вслух разные конструкции из возможных вариантов модели`. Это позволяет выявить недочеты и улучшить модель, обнаружив проблемы на ранних этапах. ![image](https://gist.github.com/assets/103507130/5ed33800-5d03-4da3-8875-91c98f58a4d3) ----- **!!! Если квалифицированные специалисты в своей области не понимают модель, то с этой моделью что-то не так** ----- ![image](https://gist.github.com/assets/103507130/eb17655f-2397-4340-986a-629184aae337) ----- #### Документация, Диаграммы, Схемы `Диаграммы` - это средства визуальной интерпретации проблемы. 1. `UML` (Unified Modeling Language): **графический язык, который с помощью диаграмм и схем описывает разнообразные процессы и структуры** **Плюсы:** `UML-диаграммы` хорошо передают отношения между объектами и неплохо показывают взаимодействия **Минусы:** `UML-диаграммы` не содержат концептуальные определения объектов ----- `Документация` - это набор текстовых, графических или других материалов, описывающих аспекты проекта, его структуру, функциональность, процессы и другую важную информацию. Это дополнение к коду и устным обсужденим. - Если термины из проектной документации не отражаются в дискуссиях и коде, это сигнал, что документ может быть слишком сложным или нефокусированным. Неактуальная или неполезная документация может стать источником путаницы в проекте. - Следует внимательно отслеживать изменения в едином языке проекта. Если документ не влияет на язык, возможно, его стоит пересмотреть или переосмыслить. ## 07. Связь между моделью и реализацией Во многих методологиях проектирования предполагается использование `аналитической модели` (analysis model) **Ее основная суть:** `Аналитическая модель` представляет собой абстракцию системы, которая используется на этапе анализа предметной области перед переходом к проектированию. - **Плюсы `analysis model`:** - переработка и усвоение знаний - служит основой для дальнейшего проектирования системы, предоставляя ясное представление о ее структуре и функциональности. `(Не всегда)` - **Минусы `analysis model`:** - `Аналитическая модель` может быть слишком абстрактной, что затрудняет непосредственное перенесение ее элементов в конкретную реализацию. - нередко не решает даже свою основную задачу – помочь в понимании предметной области, потому что ключевые открытия всегда делаются в ходе работ по проектированию и реализации приложения. #### **Тесная привязка кода к модели, лежащей в основе системы, придает коду смысл, а модели - практическую ценность.** - Разделение ответственности за анализ, моделирование, проектирование и программирование, слишком резко противоречит принципу проектирования по модели (model-driven design). **Если разработчики, пишущие код, не взаимодействуют эффективно с моделью и не осознают важность ее изменений, то модель теряет свою ценность.** - согласно принципу проектирования по модели - моделирование и программная реализация должны `«идти в связке»` - Гибкая разработка программ требует сотрудничества между участниками, сохраняя связь между моделированием и программной реализацией. - Разделение труда между моделировщиками и программистами должно быть согласованным и способствовать эффективной передаче знаний и опыта. #### Вывод: DDD стремится использовать модель для решения задач приложения, преобразуя хаотичный поток информации в работоспособную модель. Проектирование по модели создает тесную связь между моделью и реализацией, а единый язык служит каналом для эффективного обмена информацией между участниками процесса разработки. В результате достигается богатый функционал программы, основанный на глубоком понимании предметной области. ## 08. Структурные элементы предметной-ориентированного проектирования ### Как синхронизировать модель и ее реализацию Чтобы реализация программной системы шла «в ногу» с моделью необходимо применять наиболее надежные и проверенные методы моделирования и проектирования. Схема, приведенная далее демонстрирует шаблоны-образцы, которые будут представлены в этом разделе, и некоторые разновидности взаимосвязей между ними. ![image](https://gist.github.com/assets/40522320/d8f46b20-0dd7-401f-bd7e-df622c5ab85a) Следование этим стандартным шаблонам упорядочивает процесс проектирования и облегчает разработчикам понимание работы друг друга. Использование стандартных шаблонов также обогащает и дополняет единый язык, на котором все члены группы могут обсуждать модель. ### Многоуровневая архитектура Это архитектурный подход к построению ПО, при котором система делится на логические уровни, каждый из которых выполняет конкретные функции. ![image](https://gist.github.com/assets/40522320/d1aeed64-199b-473a-839b-59dc21d4c58d) #### Зачем надо? Структурные элементы и код программных систем предназначены для выполнения самых разных задач (обрабатываются данные, введенные пользователем, выполняются прикладные операции, обращение к бд). Когда код, относящийся к какой-либо структуре, размазан по огромным объемам другого кода, его становится трудно разыскивать и анализировать. Поверхностные изменения в интерфейсе пользователя могут случайно затронуть и операции алгоритмической части. ### Строение многоуровневой архитектуры Важнейший принцип многоуровневости состоит в том, что любой элемент какого-нибудь уровня зависит от работы только других элементов того же уровня или элементов более низких уровней. Ценность многоуровневости состоит в том, что каждый уровень специализируется на конкретном аспекте программы. В наиболее успешных архитектурах используются `четыре` концептуальных уровня: ![image](https://gist.github.com/assets/103886610/d2242d07-42c1-4aa3-80b2-8484aed9274f) ![image](https://gist.github.com/assets/103886610/365d543a-9338-4f9e-a377-a5a13819f1b3) ![image](https://gist.github.com/assets/103886610/bbde75b2-c1a7-4981-b932-4f1ad1b5d11b) ### Связь между уровнями Уровни должны быть связаны не жестко, и зависимость проектирования одного уровня от другого должна быть направлена только **в одну сторону.** Верхние уровни могут использовать элементы нижних или манипулировать ими напрямую. Но если объекту нижнего уровня требуется связь с верхним, то для этого нужен другой механизм `(уведомление или наблюдатель)`. - Главное, чтобы связи UI и операционного уровня обеспечивали изоляцию уровня предметной области и позволяли проектировать ее объекты, не думая о UI и его возможных обращениях к этим объектам. - Инфраструктурный уровень обычно не инициирует никаких операций на уровне предметной области. Находясь «ниже» предметной области, он не обязан знать ничего конкретного об объектах, которые он обслуживает. Например, если приложению нужно послать письмо по электронной почте, то на инфраструктурном уровне найдется для этих целей некий почтовый интерфейс, а элементы операционного уровня смогут запросить передачу сообщения. Это упрощает операционный уровень: он знает, когда послать сообщение, но ему не нужно знать, как это сделать. Уровни прикладных операций и предметной области обращаются к службам, предоставляемым инфраструктурным уровнем. ### Архитектурные среды Это окружения, которые поддерживают и облегчают проектирование и разработку систем. Если инфраструктура реализована в форме `служб (services),`, вызываемых через `интерфейсы`, то многоуровневость и разделение уровней достигается вполне естественно. Но некоторые технические проблемы требуют других форм инфраструктуры. В подобных инфраструктурных средах `(frameworks)` реализуются нужные функции инфраструктуры, и одновременно эти среды навязывают другим уровням определенные способы реализации, например, в виде подклассов одного из классов среды или с заранее заданными сигнатурами методов. Но среды могут и помешать работе: накладывая слишком много ограничений, сужающих выбор проектных решений в предметной области, либо делая реализацию тяжелой, что тормозит разработчиков. Большинства недостатков архитектурных сред удается избежать, если применять их избирательно для преодоления трудных мест, а не искать одно общее готовое решение. Скрупулезный отбор только наиболее ценных в каждом случае функций среды снижает зависимость программной реализации от этой среды, давая большую гибкость в выборе будущих проектных решений. ### Уровень предметной области - вместилище модели Во многих стилях программирования также в той или иной мере пользуются разделением на уровни. А вот в предметно-ориентированном проектировании программ требуется обязательное наличие только одного конкретного уровня. `Модель предметной области - это набор понятий`. Уровень предметной области - это выражение данной модели и все элементы программной архитектуры, имеющие к ней отношение. Уровень предметной области образуется путем проектирования и реализации всей совокупности понятий и связей в предметной области. В проектировании по модели (model-driven design) программные конструкции уровня предметной области непосредственно отражают концепции модели. ### «Антишаблон» интеллектуального интерфейса пользователя (smart UI) При разработке проекта с применением проектирование по модели есть возможность (но не везде) поместить реализацию прикладной модели `(business logic)` прямо в `интерфейс пользователя`. Разбейте приложение на небольшие функции и реализуйте их в виде отдельных пользовательских интерфейсов, помещая правила деловых регламентов `(business rules)` непосредственно в них. Для хранения данных используйте общую реляционную базу. Преимущества: - Высокая производительность труда и мгновенный результат в простых приложениях. - Менее квалифицированным разработчикам практически не требуется дополнительное обучение. - Можно преодолеть недостатки в анализе ТЗ, выпустив прототип для пользователей и затем быстро внеся нужные изменения. - Функции и операции отделены друг от друга. Расширение системы дополнением простыми функциями выполняется легко. - Реляционные базы данных справляются с задачей и обеспечивают интеграцию на уровне данных. - Применимы средства 4GL (low-code). - При передаче приложения в другие руки программисты-доработчики смогут быстро переделать фрагменты кода, которых они не понимают. Недостатки: - Сложно осуществить интеграцию (объединение) приложений - разве что через базу данных. - Отсутствует переносимость функциональных модулей и абстракция прикладных моделей. Правила прикладной модели `(businеss rules)` приходится дублировать в каждой операции, связанной с ними. - Быстрое создание опытных образцов (прототипов) с последующим итерированием затруднено, поскольку недостаток абстрагирования ограничивает возможность рефакторинга. - Сложность такой системы быстро похоронит все надежды, так что развитие возможно только в сторону дополнительных простых приложений. Невозможно «изящно» реализовать сложные функции и сложное поведение системы. Одно из закономерных следствий описываемого шаблона состоит в том, что к другой методологии проектирования невозможно перейти, не заменяя всю программу целиком. ## 09. Модель, выраженная в программе ### Ассоциации Это отношения между агрегатами и их предметными моделями. Они помогают установить связи между различными элементами доменной модели и определять, как они взаимодействуют между собой. Есть виды ассоциаций: - Один-ко-многим (1 объекту из одной стороны ассоциации соответствует много объектов из другой); - Многие-ко-многим (как в бд); - Однонаправленные ассоциации (позволяют одному агрегату ссылаться на другой, но не обратно); - Двунаправленные ассоциации (позволяют агрегатам ссылаться друг на друга в обоих направлениях). Существует как минимум 3 способа сделать ассоциации более удобными и управляемыми: 1. Свести отношения к однонаправленным, прослеживаемым в одном направлении. 2. Добавить квалификаторы, тем самым снижая кратность. 3. Устранить несущественные ассоциации. ## Сущности Это объекты, обладающие уникальной идентичностью и изменяемым состоянием, имеющие свою смысловую значимость в предметной области. Они представляют долгоживущие объекты, присутствующие в системе на протяжении времени. Они обычно описывают реальные или концептуальные объекты и представляют их внутреннее состояние и поведение. Атрибуты в свое время представляют свойства, характеристики или данные сущности, также они описывают ее состояние. Логически целостный объект, определяемый совокупностью индивидуальных черт, называется сущностью. Для таких объектов существуют особые принципы моделирования и проектирования. На протяжении их цикла существования у них может радикально меняться и форма, и содержание, но непрерывность этого существования обязана поддерживаться. Они должны идентифицироваться таким образом, чтобы их можно было однозначно отследить. Большинство `«сущностей»` в программной системе не представляют собой физические или юридические лица. Это может быть человек, город, автомобиль, лотерейный билет или банковская транзакция. ### Моделирование сущностей При моделировании объекта совершенно естественно думать о его атрибутах, и очень важно думать о его поведении, рабочих функциях. Но основная функция сущности поддерживать непрерывность своего существования таким образом, чтобы ее поведение было понятным и предсказуемым. Лучший способ добиться этого - `умеренность и экономия`. Вместо того чтобы сосредоточиться на атрибутах или даже на рабочих функциях объекта, следует ограничить определение объекта-сущности наиболее неотъемлемыми его характеристиками, т.е. теми, которые однозначно выделяют его среди других и обычно используются для его поиска и сравнения. ![image](https://gist.github.com/assets/40522320/8bb00402-04a8-4a51-ba7a-a7f816d2d324) Атрибут `customerID` - это единственный реальный идентификатор объекта `Клиент (Customer)`, но для поиска или сопоставления личности Клиента может также использоваться номер телефона или адрес. Имя не определяет индивидуальность человека, но входит в число средств, позволяющих проверить ее подлинность. В данном примере атрибуты номера телефона и адреса помещены в объект Клиент, но в реальном проекте для такого решения были бы приняты во внимание типичные способы различения или сопоставления клиентов. Например, если у Клиента есть много номеров телефонов для разных целей, то номер нельзя ассоциировать с индивидуальной личностью клиента, и его следует вынести в Деловые контакты (Sales Contact). ### Проектирование операций идентификации Это нахождение оптимального подхода к идентификации и управлению идентификаторами сущностей в предметной модели. Для каждого объекта-сущности должен быть задан способ сопоставления его с другим объектом - такой, который бы позволял различать их даже в случае совпадения описательных атрибутов. **Идентифицирующий атрибут должен гарантированно оставаться уникальным в пределах системы, независимо от способа ее определения** Если из атрибутов объекта нельзя составить действительно уникальный ключ, то есть другое типовое решение - каждому экземпляру присвоить символ-идентификатор `(id)`. Идентификаторы часто генерируются системой автоматически. При автоматическом генерировании идентификатора у пользователя может даже не возникнуть потребность его видеть. Идентификатор может понадобиться только для внутренних целей. > В некоторых случаях уникальность идентификатора не должна ограничиваться пределами компьютерной системы. Например, если между двумя больницами, имеющими отдельные компьютерные системы управления, идет обмен медицинскими картами, то каждая система должна обозначать пациента одним и тем же `id`. Часто используются специальные `идентификаторы`, а другие атрибуты, такие как номера `телефонов и социального страхования`, служат для **сопоставления и проверки**. ## Объект-значение Это тип объекта, который представляет описательный аспект предметной области и не имеет `собственной идентичности (id)`. Такие объекты создаются в программе для представления тех элементов проекта, о которых достаточно знать только, что они собой представляют, но не кем именно они являются. Объект-значение может представлять собой совокупность других объектов, или же ссылаться на сущности. Также они часто передаются в качестве параметров в сообщениях между объектами. Нередко они носят временный характер - создаются для конкретной операции и тут же уничтожаются. ### Отличие В программе для фирмы, торгующей по почте, адрес необходим для подтверждения кредитной карточки и отправки посылки. Но если там же заказывает товар сосед по квартире, фирме совершенно безразлично, что он находится по тому же адресу. Здесь адрес - это `объект-значение`. В программе обслуживания почтовых отправлений, предназначенной для организации маршрутов доставки почты, страна может быть представлена в виде иерархии областей, городов, индексных зон и жилищных массивов, которая заканчивается отдельными адресами. Такие адресные объекты наследуют свой почтовый индекс от родительского объекта в иерархии, и если почтовое управление решит изменить индексные зоны, все адреса в их пределах «подстроятся» под это изменение. Здесь адрес является `сущностью`. ### Проектирование объектов-значений Объект-значение нужно сделать `неизменяемым` запрещенным для любых изменений иначе как путем полной замены. > Один и тот же объект `имя` может совместно использоваться двумя объектами Человек (в каждом будет содержаться указатель на один и тот же экземпляр Имени), и при этом никаких изменений в их поведении или способе идентификации не потребуется. То есть, они будут работать правильно, пока у одного человека не изменится имя. > Та же проблема возникает, когда объект передает один из своих атрибутов другому объекту в качестве аргумента. Значение передаваемого объекта может измениться таким образом, что будет поврежден и объект-владелец путем искажения его инвариантов. Чтобы избежать этого, передаваемый объект делают `неизменяемым или же передают его копию`. Сравнительная экономичность копирования и совместного использования зависит от среды реализации. Копирование может `«затопить»` систему огромным количеством объектов, зато совместное использование способно замедлить работу распределенной сетевой системы. Совместное использование объектов стоит ограничить теми случаями, когда оно приносит наибольшую пользу и доставляет меньше всего неприятностей: - если занимаемый объем памяти или количество объектов в базе данных являются критическими параметрами; - если дополнительные затраты на пересылку по сети невелики; - если совместно используемый объект категорически неизменяем. Модификация объектов-значений применяется в следующих случаях: - частые изменения значения объекта (т.е. собственно объекта-значения); - затратность создания и уничтожения объекта; - опасность замены (вместо модификации) при группировании объектов; - незначительное совместное использование или же полный отказ от него с целью улучшения группирования объектов или из каких-то других технических соображений. ### Проектирование ассоциаций с помощью объектов-значений В то время как двунаправленные ассоциации между сущностями бывает трудно поддерживать, двунаправленные ассоциации между объектами-значениями вообще не имеют смысла. При отсутствии индивидуальности у объектов бессмысленно говорить, что тот или иной объект ссылается на тот же объект-значение, который ссылается на него. ## Службы (services) Это компоненты, которые предоставляют `операции и функциональность`, не относящиеся к конкретной сущности или агрегату, но связанные с основными бизнес-потребностями предметной области. В прикладной предметной области бывают такие операции, которым нельзя найти естественное место в объекте типа сущности `(entity)` или значения `(value object)`. Они по своей сути являются не предметами, а видами деятельности. У них нет ни собственного характерного состояния, ни другой роли в предметной области, кроме реализации конкретной операции. Хорошая служба обладает тремя свойствами: 1. Выполняемая ею операция соответствует понятию модели, не являющемуся естественной частью объекта-сущности или объекта-значения. 2. Интерфейс службы определен через другие элементы модели предметной области. 3. Операция не имеет собственного состояния. > Отсутствие состояния - означает, что любой клиент может воспользоваться любым экземпляром нужной ему службы, не обращая внимания на индивидуальную предысторию ее работы. ### Службы и изоляция уровня предметной области В основе этого шаблона лежат такие службы, которые сами по себе много значат для предметной области. Но, их применение не ограничивается одним этим уровнем. Не так уж легко отличить службы, принадлежащие к уровню предметной области, от служб других уровней, и соответственно разделить обязанности, чтобы четко обозначить это различие. > Операционный уровень отвечает за то, чтобы отдать приказ об извещении клиента. А уровень модели определяет, достигнут ли порог - правда, эта задача не требует отдельной службы, а скорее, входит в полномочия объекта «банковский счет». Та же самая банковская программа, занимается и переводом денежных средств. Если сконструировать службу, отвечающую за подведение дебета и кредита в процессе перевода, то она будет принадлежать уровню предметной области. ### Распределение служб по уровням ![image](https://gist.github.com/assets/103886610/6f160291-847d-4ace-8516-2f90922a9a99) ## Степень модульности Это уровень разделения и независимости компонентов и модулей в предметной модели. Службы ценны тем, что помогает управлять крупностью разбиения `(степенью модульности)` в интерфейсах уровня предметной области, а также изоляцией клиентов от сущностей и объектов-значений. `Средней крупности службы`, не имеющие собственного состояния, легче переносить в большие системы, поскольку в них за простым интерфейсом инкапсулируются большие функциональные возможности. Если же объекты имеют слишком мелкий масштаб, в распределенной системе это может привести к неэффективному обмену сообщениями. Сложность взаимодействия, слишком загроможденного мелкими деталями, в конце концов, передается на операционный уровень. Знание из модели потихоньку `«растекается»` по коду прикладных операций или пользовательского интерфейса, уходя с положенного ему уровня. Разумное же применение служб уровня предметной области позволяет удерживать четкую границу между уровнями. В этом архитектурном шаблоне простоте интерфейса отдается предпочтение перед разнообразием функций и управлением со стороны клиента. Разбиение `(модульность)` функциональных возможностей поддерживается на среднем уровне, как раз удобном для `инкапсулирования компонентов` в больших или распределенных системах. К тому же, иногда служба - это самый естественный способ выразить понятие из модели предметной области. ## Модули Это логический компонент, который содержит связанный набор классов, функций или операций, имеющих общую ответственность и задачи в предметной области. Модули дают возможность посмотреть на модель с разных сторон: - во-первых, можно изучить подробности устройства модуля, не вникая в сложное целое; - во-вторых, удобно рассматривать взаимоотношения между модулями, не вдаваясь в детали их внутреннего устройства. Когда два элемента модели разводятся по разным модулям, взаимосвязь между ними становится не такой прямой, как раньше. Из-за этого бывает труднее понять, какое место они занимают в архитектуре программы. Если минимизировать зависимость между модулями, задача облегчается, и становится возможным анализировать содержимое одного модуля, почти не обращаясь к тем, с которыми он взаимодействует. В тоже время элементы хорошей модели склонны к `синергизму` (совместное действие создает большую ценность, чем простое сложение отдельных частей), и если подразделение на модули выбрано удачно, то взаимоотношения между элементами модели выходят на новый концептуальный уровень. ## Ловушка инфраструктуры Сильное влияние на проектные решения по организации пакетов оказывают технические среды программирования. Пример стандарта, задаваемого средой - введение многоуровневой архитектуры путем помещения кода инфраструктуры и пользовательского интерфейса в разные группы пакетов. Вследствие этого уровень предметной области (модели) тоже физически выделяется в свой, `отдельный набор пакетов`. С другой стороны, многоярусные архитектуры могут привести к слишком фрагментированной реализации объектов модели. Программирование в рамках архитектурных сред преследует две вполне законные цели. - Во-первых, это логическое разделение обязанностей: один объект отвечает за доступ к базе данных, другой - за операции прикладной модели и т.д. Такое разделение позволяет легче понимать работу каждого уровня и свободнее переключаться между ними. Беда в том, что при этом не осознается, какую цену приходится платить разработчикам. **Но даже если бы других вариантов не было, лучше было бы отбросить все эти преимущества ради более связного уровня предметной области.** Сложные схемы разбиения на пакеты, базирующиеся на технических соображениях, имеют две особенности: - если требования архитектурной среды к распределению обязанностей таковы, что элементы, реализующие объекты, оказываются физически разделенными, то код больше не выражает модель; - нельзя разделять до бесконечности - у человеческого ума есть свои пределы, до которых он еще способен соединять разделенное; если среда выходит за эти пределы, разработчики предметной области теряют способность расчленять модель на осмысленные фрагменты. > Если нет реальной необходимости распределять код между разными серверами, храните весь код, реализующий один концептуальный объект, в одном модуле, а то и классе. > >Используйте разбиение на пакеты только для того, чтобы `отделить уровень предметной области` от остального кода. ## 10. Цикл существования объектов модели Каждый объект имеет свой цикл существования. Объект `«рождается»`, затем, как правило, проходит ряд сменяющихся состояний, а потом `«умирает»` - либо удаляется, либо помещается в архив. ![image](https://gist.github.com/assets/78319818/7fc4b051-f584-464b-9910-6a4938fee854) Многие из объектов - `простые` и `временные`; они создаются после вызова конструктора, используются в вычислительных операциях, после чего их оставляют сборщику мусора. Усложнять такие объекты ни к чему. Но у некоторых объектов существование может продлиться намного дольше и частично пройти не в оперативной памяти. Они имеют сложные связи и отношения с другими объектами и проходят через изменения состояния, подчиняющиеся определенным инвариантам. Управление такими объектами не обходится без трудностей, которые легко могут привести к нарушению принципов проектирования по модели `(model-driven design)`. Эти трудности делятся на две категории: - Поддержание целостности объекта на этапе его существования. - Предотвращение излишней сложности в управлении циклом существования объектов. ## Решение Указанные проблемы будут решаться на основе трех архитектурных шаблонов. - `агрегаты` (aggregates) помогут «подтянуть» саму модель и избавиться от хаотической путаницы разнообразных объектов, четко определив права собственности и границы. Этот шаблон играет решающую роль в поддержании целостности объектов на всех этапах их существования. - `фабрики` (factories) для создания и восстановления сложных объектов и агрегатов с сохранением инкапсуляции их внутренней структуры. - `хранилища` (репозитории), середина и конец жизненного цикла объектов - это компетенция `(repositories)`, которые позволяют находить и извлекать постоянно хранимые объекты, при этом инкапсулируя гигантскую инфраструктуру, стоящую за этими операциями Хранилища и фабрики непосредственно не происходят из предметной области, они играют важную смысловую роль в ее архитектуре. Моделирование агрегатов наряду с добавлением в программу фабрик и хранилищ дает нам возможность манипулировать объектами систематически, в естественных смысловых границах, на протяжении всего цикла их существования. `Агрегаты` обозначают область действия, в пределах которой на каждом этапе жизненного цикла должны удовлетворяться определенные инварианты. Фабрики и хранилища оперируют агрегатами, инкапсулируя сложности специфических переходных этапов их существования. ## Агрегаты `Агрегат` - это кластер сущностей и объектов-значений, объединённых общими инвариантами. Любое взаимодействие с агрегатом осуществляется через одну и только одну из его сущностей, называемую `корнем агрегата`. ## Инварианты При внесении изменений в объекты модели, обладающей сложной системой ассоциаций, трудно гарантировать согласованность этих изменений. Необходимо соблюдать `инварианты`, относящиеся к тесно связанным группам объектов, а не отдельным объектам. Но если применить слишком осторожные схемы блокирования, то параллельные пользователи станут невольно мешать друг другу, и это сделает всю систему неработоспособной. `Инвариант (invariant)`. Контрольное утверждение `(assertion)` относительно некоторого элемента архитектуры, которое должно быть истинным всегда, кроме специфических переходных состояний, таких как процесс выполнения метода или незавершенной транзакции базы данных. (с) Э. Эванс `Инвариант` - это бизнес-правило, которое всегда сохраняет свою непротиворечивость. Это явление называется транзакционной согласованностью `(transactional consistency)`, которая считается мгновенной и атомарной. Кроме того, существует итоговая согласованность `(eventual consistency)`. Обсуждая инварианты, мы говорим о транзакционной согласованности. (с) В. Вернон В любой системе с постоянным хранением данных у каждой транзакции, которая вносит в данные изменения, должна быть своя ограниченная область действия. Кроме того, должен быть способ поддерживать согласованность данных (путем соблюдения их инвариантов). В базах данных можно применять разные схемы блокирования, а также программировать тесты. Но эти половинчатые решения отвлекают нас от модели, и очень скоро положение только усугубится. ### Пример (что такое инварианты и агрегаты) >Пусть, например, в авторемонтной мастерской используется программа, в которой построена модель автомобиля. Автомобиль представляет собой сущность с глобальной идентичностью - его необходимо отличать от всех остальных машин в мире, даже очень похожих. Для этой цели подходит номерной знак, поскольку это индивидуальный идентификатор, присвоенный каждому автомобилю. Нам может понадобиться проследить историю использования шин в их четырех возможных положениях на колесах, узнать их пробег и степень износа. Чтобы точно знать, где какая шина, их тоже следует сделать сущностями. Но очень маловероятно, что их индивидуальность будет нас интересовать вне контекста конкретного автомобиля. Если заменить шины и отослать старые на переработку, то либо наша программа вообще перестанет их отслеживать, либо они станут анонимными членами кучи списанных шин. Их пробег уже никого не будет волновать. Но более уместно к теме будет заметить, что даже если шины установлены на машине, никто не попытается ввести в систему запрос на поиск конкретной шины, чтобы потом посмотреть, на какой машине она стоит. Наоборот, в базу данных будет послан запрос на поиск машины, а потом запрос на временную ссылку, ведущую к шинам. Поэтому автомобиль является корневой сущностью в агрегате, граница которого охватывает также и шины. С другой стороны, двигатели имеют собственные, выбитые на них серийные номера, по которым их иногда разыскивают независимо от автомобилей. В некоторых приложениях двигатели могут быть корневыми объектами своих собственных агрегатов. >![image](https://gist.github.com/assets/78319818/daf9b479-177e-47f4-9f43-b5bdd91cd881) >Из взаимосвязей между объектами агрегата можно составить так называемые `инварианты`, т.е. правила совместности, которые должны соблюдаться при любых изменениях данных. Не всякое правило, распространяющееся на агрегат, обязано выполняться непрерывно. Восстановить нужные взаимосвязи за определенное время можно с помощью `обработки событий`, `пакетной обработки` и других механизмов обновления системы. Но соблюдение инвариантов, имеющих силу внутри агрегата, должно контролироваться немедленно по завершении любой транзакции. ![image](https://gist.github.com/assets/78319818/93b50fd0-a98b-49b4-9c08-636f5c495538) ### Как реализовать агрегат Чтобы реализовать концепцию агрегата в виде практической программной конструкции, необходимо иметь набор правил, выполняющихся для любой транзакции: - Корневой объект-сущность имеет глобальную идентичность и несет полную ответственность за проверку инвариантов. - Некорневые объекты-сущности имеют локальную идентичность - они уникальны только в границах агрегата. - Нигде за пределами агрегата не может постоянно храниться ссылка на что-либо внутри него, кроме его корневого объекта. Корневой объект-сущность может передавать ссылки на внутренние объекты-сущности другим объектам. Но эти другие объекты могут использовать их только временно, и не имеют права хранить их или как-то фиксировать. Корневой объект может передать копию объекта-значения другому объекту, и что с ней потом случится не играет роли, поскольку это не более чем значение, и оно не имеет никаких связей с агрегатом. - Как следствие из предыдущего правила, только корневые объекты агрегатов можно непосредственно получать по запросам из базы данных. Все остальные объекты разрешается извлекать только по цепочке связей. - Объекты внутри агрегата могут хранить ссылки на корневые объекты других агрегатов. - Операция удаления должна одновременно ликвидировать все, что находится в границах агрегата. (При наличии сборки мусора это просто. Поскольку внешних ссылок ни на что, кроме корневого объекта, не существует, достаточно удалить корневой объект, а остальное будет подчищено при сборке.) - Как только вносится изменение в любой объект внутри границ агрегата, следует сразу удовлетворить все инварианты этого агрегата. `Группируйте` сущности и объекты-значения в агрегаты и определяйте границы каждого из них. Выберите один объект-сущность и сделайте его корневым. Осуществляйте все обращения к объектам в границах агрегата только через его корневой объект. Разрешайте внешним объектам хранить ссылки только на корневой объект. Ссылки на внутренние объекты агрегата следует передавать только во временное пользование, на время одной операции. Поскольку доступ к объектам агрегата контролируется через корневой объект, неожиданные изменения внутренних объектов невозможны. В такой схеме разумно требовать удовлетворения всех инвариантов для объектов в агрегате и для всего агрегата в целом при любом изменении состояния. ## Фабрики Если создание объекта или целого агрегата представляет большую сложность или открывает постороннему глазу слишком много внутренней структуры, то нужную инкапсуляцию обеспечивают фабрики `(factories)`. В значительной мере сила такого инструмента, как объекты, заключается в его сложном внутреннем устройстве, включая и ассоциации между его элементами. Объект следует `«дистиллировать»` (т.е. очищать от всего лишнего), пока в нем не останется ничего, что не имеет отношения к самой его сути и его роли в транзакциях. С объекта довольно и этих обязанностей, свойственных середине цикла его существования. Если на сложный объект возложить еще и ответственность за создание самого себя, то начинают возникать проблемы. ![factories](https://gist.github.com/assets/78319818/6035871e-20ea-4f42-a59c-02d284506d7e) > ### Для чего нужно? > Перекладывание ответственности на другую заинтересованную сторону, `объект-клиент`, приводит к еще худшим последствиям. Клиент знает, какую работу нужно сделать, и поручает необходимые вычислительные операции объектам предметной области. Если от клиента требуется самому собирать те объекты модели, которые для этого нужны, то он должен знать достаточно об их внутреннем устройстве. А чтобы соблюсти все инварианты, касающиеся взаимоотношений между отдельными частями объекта модели, клиент должен знать еще и некоторые внутренние правила (деловые регламенты) этого объекта. Даже простой вызов конструктора привязывает объект-клиент к конкретным классам объекта, который он создает. Никакие изменения в реализацию объектов модели теперь невозможно внести, не изменяя и объект-клиент, отчего становится труднее выполнять рефакторинг. > >Когда создание объекта модели перекладывается на объект-клиент, это приводит к лишним сложностям и делает менее четким распределение обязанностей. К тому же, образуется брешь в инкапсуляции создаваемых объектов прикладной модели и агрегатов. Что еще хуже, если клиент находится на операционном уровне, то происходит «утечка» части обязанностей с уровня предметной области. Тесная привязка операционного уровня к подробностям реализации модели сводит на нет большинство преимуществ абстрагирования на уровне предметной области и сильно усложняет внесение дальнейших изменений. *Создание объекта само по себе может быть очень важной операцией. Но сборка объекта - это совершенно не та работа, которую потом придется делать этому же объекту в ходе своего существования. Объединение этих обязанностей в одном объекте порождает неуклюжие, трудные для понимания программные конструкции. Если дать клиенту возможность управлять созданием объектов, это искажает архитектуру самого клиента, нарушает инкапсуляцию собранного объекта или агрегата, а также слишком сильно привязывает клиента к конкретной реализации создаваемого им объекта* Создание сложных объектов - это обязанность `уровня предметной области`, но не объектов, которые непосредственно выражают модель. Бывают случаи, когда создание и сборка объекта соответствуют существенному событию в предметной области - например, «открытию банковского счета». Но сами по себе операции создания и сборки объекта обычно не имеют смысла в модели; это уже вопрос программной реализации. Чтобы решить эту проблему, приходится добавлять в модель конструкции, не являющиеся ни сущностями `(entities)`, ни объектами-значениями `(value objects)`, ни службами `(services)`. Здесь мы отходим от принципов определяющих суть предметной области, поэтому важно подчеркнуть еще раз: мы добавляем в модель элементы, которые ничему в ней непосредственно не соответствуют, но, тем не менее, выполняют обязанности, относящиеся к уровню модели *Передайте обязанности по созданию экземпляров сложных объектов и агрегатов отдельному объекту, который сам по себе может не выполнять никаких функций в модели предметной области, но, тем не менее, является элементом ее архитектуры. Обеспечьте интерфейс, который бы инкапсулировал все сложные операции сборки объекта и не требовал от клиента ссылаться на конкретные классы создаваемого объекта. Создавайте агрегаты как единое целое, контролируя выполнение инвариантов.* ## Методы создания фабрик Фабрики можно проектировать по-разному. Существует несколько специальных архитектурных шаблонов для создания объектов - `factory method (фабричный метод)` - `abstract factory (абстрактная фабрика)` - `builder (строитель).` Наша цель - не углубиться в подробности проектирования фабрик, а понять, что они занимают важное место в архитектуре предметной области. Правильное пользование фабриками - залог успешного проектирования по модели `(model driven design)`. Любая хорошая фабрика должна отвечать `двум` фундаментальным требованиям. 1. Каждый метод создания объекта должен быть един и неделим; он должен гарантировать соблюдение всех инвариантов создаваемого объекта или агрегата. Фабрика должна уметь создавать только объект целиком в корректном состоянии. Для объекта-сущности это означает создание сразу целого агрегата с соблюдением всех инвариантов; только необязательные второстепенные элементы разрешается добавить позже. Для неизменяемого объекта-значения это означает, что все атрибуты инициализируются окончательными корректными значениями. Если интерфейс позволяет клиенту запросить создание некорректного объекта, то должна инициироваться исключительная ситуация или запуститься какой-то другой механизм, не позволяющий возвратить некорректное значение. 2. Абстрагировать фабрику следует к желаемому типу, а не к конкретному классу. ## Фабрики vs Конструкторы Взвешивая «за» и «против», обычному общедоступному (public) конструктору нужно отдать предпочтение в следующих обстоятельствах. - Класс является типом. Он не входит ни в какую интересную иерархию и не используется полиморфически для реализации интерфейса. - Клиенту нужно знать реализацию объекта - возможно, с точки зрения выбора стратегии. - Все атрибуты объекта доступны клиенту, так что в конструкторе, предоставляемом клиенту, не создаются никакие новые объекты. - Создание объекта не является сложным процессом. - Общедоступный конструктор должен следовать тем же правилам, что и фабрика: это должна быть единая, неделимая операция, которая подходит для всех инвариантов создаваемого объекта. ***Избегайте вызывать конструкторы в конструкторах других классов. Конструкторы должны быть проще простого. Сложную сборку, особенно агрегатов, поручите фабрикам. И порог, за которым нужно вместо конструктора выбирать небольшой фабричный метод, совсем не высок.*** ## Как создавать фабрики - Каждая операция должна быть единой и неделимой. Все данные, которые необходимы для создания готового продукта-объекта, нужно передать за одну коммуникационную операцию с фабрикой. Придется также решить, что делать, если создание объекта сорвется, - например, в случае несоблюдения каких-то его инвариантов. Можно инициировать исключительную ситуацию или возвратить нулевое значение. Будьте последовательны и примите единый стандарт программирования для работы с ошибками создания объектов в фабриках - Фабрика должна быть связана со своими аргументами. Если неосторожно выбрать набор входных параметров, можно создать целую паутину взаимосвязей. На степень зависимости влияют операции, которые выполняются над аргументом. Если он просто вставляется в создаваемый объект, эта зависимость невелика. Но если при конструировании объекта из аргумента берутся фрагменты, зависимость становится сильнее. *Самые безопасные параметры - это те, которые поступают с нижних уровней архитектуры. Даже в пределах одного уровня существует тенденция разделения на подуровни: более элементарные объекты используются более сложными.* *Используйте абстрактный тип аргументов, а не конкретные их классы. Фабрика привязана к конкретному классу продуцируемых объектов; нет нужды привязывать ее еще и к конкретным параметрам.* ## Отличия между фабриками сущностей и фабриками объектов-значений Фабрики сущностей и фабрики объектов-значений отличаются друг от друга **двумя** особенностями. - Объекты-значения неизменяемы; продукт создается в окончательном виде. Поэтому операции фабрики должны давать полное описание продукта. - Фабрики сущностей же склонны работать только с самыми существенными атрибутами, необходимыми для создания корректного агрегата. Детали можно добавить и позже, если они не требуются немедленно для соблюдения инварианта. - Есть еще проблемы, связанные с присвоением идентификационных данных сущности - помимо объектов-значений. Идентификатор может назначаться программой автоматически или предоставляться снаружи. Контроль над процессом присвоения идентификатора программой удобно возложить на фабрику. Генерирование уникального идентификационного номера обычно выполняется процедурой базы данных или другим инфраструктурным механизмом, фабрика знает, что именно запрашивать и куда это поместить. *Фабрика, используемая для восстановления объекта, очень похожа на фабрику для его создания, если не считать двух основных отличий* - Фабрика, восстанавливающая объект-сущность, не присваивает ему новый идентификационный номер. Если бы она это делала, то предыдущее воплощение объекта потерялось бы. Поэтому идентификационные атрибуты должны содержаться во входных параметрах фабрики, занимающейся восстановлением объекта. - Фабрика, восстанавливающая объект, по-другому обрабатывает нарушение инварианта. В ходе создания нового объекта фабрика просто сбрасывает его, если не удовлетворяется инвариант, но при восстановлении требуется более гибкий подход. Если объект уже существует где-то в системе (например, в базе данных), этот факт нельзя просто проигнорировать. Но нельзя игнорировать и нарушение регламентов. Должна существовать какая-то схема для разрешения таких противоречий, отчего восстановление становится более сложной задачей, чем создание новых объектов. ## Репозитории (Хранилища) Благодаря наличию ассоциаций можно найти один объект по его взаимосвязям с другими объектами. Но для этого нужно иметь отправную точку, отталкиваясь от которой можно проследить сущность или объект-значение в середине их цикла существования. >Чтобы делать с объектом что бы то ни было, нужно иметь ссылку на него. Как получить эту ссылку? Один из способов - создать объект, поскольку при создании объекта возвращается ссылка на него. Второй способ - проследить ассоциацию. Начинаем с объекта, который нам уже известен, и запрашиваем у него информацию о связанном с ним объекте. Во всякой объектно-ориентированной программе это происходит постоянно. Именно в таких взаимосвязях заключается основная выразительность объектных моделей. Но нужно где-то взять тот самый отправной объект, с которого все начинается. ***Клиентское приложение нуждается в удобных средствах получения ссылок на существующие объекты предметной области. Если инфраструктура позволяет это делать относительно легко, разработчики клиента добавляют в модель больше прослеживаемых ассоциаций, загромождая ее. С другой стороны, они могут с помощью запросов извлекать из базы данных в точности ту информацию, которая им нужна - например, достать несколько конкретных подобъектов, не обращаясь к корневому объекту агрегата. Таким образом операционная логика предметной области переносится в запросы и клиентский код, а роль сущностей и объектов-значений сводится к простым контейнерам данных. Техническая сложность реализации всей инфраструктуры доступа к базе данных быстро загромождает код клиента, вынуждая разработчиков урезать и упрощать уровень предметной области. В итоге модель становится бесполезной.*** > Вот бы иметь такой метод доступа, который позволил бы не отступить от этих принципов И сохранить именно модель в центре внимания при разработке программы. Для начала не следует беспокоиться о временных объектах. Такие объекты (обычно объекты-значения) проживают короткую жизнь - они используются в операциях создавших их клиентов, а затем сбрасываются. Что касается постоянных объектов, то для работы с ними не надо использовать запросы к базе данных, если их удобнее находить по цепочке ассоциаций. Например, адрес человека можно узнать из объекта Человек (Person). И что самое важное, обращение к любому объекту, внутреннему по отношению к агрегату, может выполняться только через корневой объект агрегата. > *Необходимо, чтобы какая-то часть постоянно существующих объектов была доступна через глобальный поиск по атрибутам. Доступ таким методом осуществляется к корневым объектам агрегатов, которые неудобно отслеживать по ассоциациям. Обычно это сущности, иногда - объекты-значения со сложной внутренней структурой, а иногда значения из перечислимых типов. Предоставление такого доступа к другим объектам стирает основные различия между разновидностями объектов. Отсутствие ограничений на запросы к базе данных может нарушить инкапсуляцию объектов и агрегатов предметной области. Открытое использование технической инфраструктуры и механизмов доступа к базам данных усложняет работу клиентского приложения и знаменует отход от принципов проектирования по модели.* `Шаблон хранилища (repository)` - это концептуально несложная архитектура, позволяющая инкапсулировать технические решения доступа к базам данных и сконцентрировать внимание на прикладной модели. Хранилище представляет все объекты определенного типа в виде концептуального множества (обычно виртуального, эмулируемого). Оно работает аналогично коллекции, только с более развитым механизмом запросов. Можно добавлять и удалять объекты соответствующего типа, и скрытые механизмы хранилища будут автоматически помещать их в базу данных или удалять из нее. Введя это определение, получаем полный набор средств для доступа к корневым объектам агрегатов с самого начала и до конца их цикла существования. Наличие хранилища снимает с клиента огромную тяжесть - теперь он может общаться с простым, скрывающим технические подробности интерфейсом, и запрашивать нужную ему информацию в терминах модели. Для поддержки всего этого нужна развитая и сложная техническая инфраструктура, но сам интерфейс остается простым и концептуально привязанным к модели предметной области. *Для каждого типа объектов, к которым требуется глобальный доступ, введите объект-посредник, который может создать иллюзию, что все объекты такого типа объединены в коллекцию и находятся в оперативной памяти. Наладьте доступ через хорошо известный глобальный интерфейс. Реализуйте методы для добавления и удаления объектов, инкапсулирующие реальное помещение информации в базу данных и удаление ее оттуда. Реализуйте методы, которые будут выбирать объекты по заданным критериям и возвращать полностью сгенерированные и инициализированные объекты или коллекции объектов с атрибутами, подходящими под критерии, таким образом инкапсулируя реальные технологии хранения данных и выполнения запросов. Реализуйте хранилища только для тех агрегатов, к корневым объектам которых требуется прямой доступ. Программа-клиент должна опираться на модель, а все операции хранения и обработки данных объектов должны быть переданы хранилищам.* ## Преимущества хранилищ У хранилищ есть ряд важных преимуществ, ведь они: - предоставляют клиентам простую модель для получения устойчиво существующих объектов и управления их жизненным циклом; - убирают из операционной части приложения и модели предметной области необходимость в технической поддержке целостности объектов, разных вариантов технологий СУБД, и даже разных источников данных; - выражают в себе проектные решения по способам доступа к объектам; - позволяют легко заменить себя «заглушками» для целей тестирования (обычно заглушкой служит находящаяся в оперативной памяти коллекция). Все хранилища должны содержать методы, с помощью которых клиенты могут запрашивать объекты, соответствующие некоторым критериям. Но вот в организации такого интерфейса возможны варианты. **Одно из особенно удачных решений для обобщения хранилищ путем создания архитектурной среды состоит в том, чтобы строить запросы на основе спецификаций `(specification)`. Спецификация позволяет клиенту описывать (т.е. задавать, специфицировать), что именно ему нужно, и при этом не беспокоиться, как именно это делается. В процессе этого создается объект, который фактически и осуществляет нужный выбор.** *Даже если хранилище спроектировано на выполнение гибких запросов, оно должно позволять также и добавлять специализированные, явно прописанные запросы. Это могут быть вспомогательные методы, инкапсулирующие часто используемые запросы, или такие, которые возвращают не сами объекты, а, например, результаты определенных математических операций с ними. Среды, которые не предусматривают такую возможность, в конце концов, либо «замутняют» чистоту архитектуры предметной области, либо просто игнорируются разработчиками.* ***Инкапсуляция механизмов хранения данных, их извлечения и выполнения запросов - это самое основное в реализации хранилища.*** ## Как реализовать хранилище (репозиторий) Концепцию хранилища можно адаптировать ко многим ситуациям. Возможности ее реализации настолько разнообразны, что здесь будет приведено только несколько общих принципов, которые полезно помнить. - Абстрагируйте тип. Хранилище как бы «содержит в себе» все экземпляры определенного типа, но это не значит, что для каждого класса надо иметь свое хранилище. В качестве типа можно использовать абстрактный надкласс из иерархии. Тип также может представлять собой интерфейс, реализаторы которого даже не связаны иерархически. Но это может быть и один конкретный класс. Не забывайте, что всегда можно встретить препятствия на пути реализации всего этого о из-за отсутствия подобного полиморфизма в технологии СУБД. - Извлекайте преимущества из независимости от клиента. Реализация хранилища предоставляет значительно больше свободы для внесения изменений, чем тот случай, когда клиент вызывает механизмы напрямую. Этим можно воспользоваться для оптимизации быстродействия, варьируя запросы или кэшируя объекты в памяти, по желанию меняя общую методику хранения и поддержания целостности объектов. Можно также облегчить тестирование клиентского кода и объектов модели предметной области, построив легко управляемую симуляцию хранилища с хранением объектов в оперативной памяти. - Оставьте контроль транзакций клиенту. Хотя именно хранилище помещает данные в базу и извлекает их оттуда, оно, как правило, не должно контролировать их завершение (т.е. выполнять фиксацию транзакции). Конечно, есть искушение, например, зафиксировать транзакцию после сохранения данных, но у клиента наверняка есть собственный контекст для корректной инициализации и завершения отдельных рабочих операций. Контроль транзакций со стороны клиента значительно облегчается, если хранилище в это дело не вмешивается. ## Связь с фабриками Фабрика ведает началом существования объекта, а хранилище помогает работать с ним в середине и конце его жизни. Если объекты находятся в оперативной памяти или хранятся в объектной базе данных, то тут все просто. Но обычно хотя бы часть данных программы сохраняется в реляционной базе, файле и других необъектных системах. В таких случаях извлеченные из этих мест хранения данные приходится восстанавливать в объектную форму Поскольку в этом случае хранилище фактически создает объекты по имеющимся данным, многие считают, что хранилища - и есть фабрики. С технической точки зрения так оно и есть. Но все-таки полезно на первом плане держать концептуальную модель, а с ее позиций, как уже говорилось, восстановление хранимого объекта не есть создание нового. В методике проектирования, основанной на предметной области, фабрики и хранилища выполняют разные функции. Фабрика создает новые объекты; хранилище находит и извлекает старые. Хранилище должно давать клиентам иллюзию, что объекты хранятся прямо в памяти. Бывает, что объекты приходится восстанавливать (да, и при этом создавать новые экземпляры), но концептуально это те же самые объекты, которые уже существовали - это просто середина их жизненного цикла. Чтобы примирить разные точки зрения, достаточно сделать так, чтобы хранилище делегировало создание объектов фабрике, которая также (теоретически, хотя на практике и редко) могла бы создавать и совсем новые объекты «с нуля». Четкое разделение этих обязанностей помогает также снять с фабрики всякую ответственность за поддержание целостности (непрерывности существования) объекта. Работа фабрики - создать объект любой требуемой сложности на основе данных. Если в результате получается новый объект, об этом должен знать клиент, который при желании может добавить его в хранилище, а оно уже инкапсулирует операции по сохранению объекта в базе данных. `Хранилище восстанавливает существующий объект с помощью фабрики.` Искушение объединить фабрику и хранилище появляется еще в одном случае – при желании реализовать функцию `«поиска или создания»`. При этом клиент описывает, какой объект ему нужен, и если поиск показывает, что такого объекта еще не существует, то он создается и предоставляется клиенту. Подобных функций следует избегать. В лучшем случае ее наличие создает совсем небольшие удобства, но даже кажущаяся ее полезность исчезает вовсе, если в программе делается различие между сущностями и объектами-значениями. Если клиенту нужен объект-значение, он обращается к фабрике и получает новый. Как правило, различие между новым и уже существующим объектами играет важную роль в предметной области, и если средства архитектурной среды позволяют сымитировать отсутствие такого различия, то на самом деле они только запутывают дело. ## Проектирования объектов для реляционной БД База данных более тесно связана с объектной моделью, чем большинство прочих компонентов. База данных не просто имеет дело с объектами - она хранит в себе постоянную форму тех данных, которые образуют эти самые объекты. Существуют достаточно отлаженные средства для построения соответствий между этими двумя формами данных и управления ими. Не считая технических трудностей, некорректное построение такого соответствия может иметь существенное влияние и на саму объектную модель. Наиболее распространены три случая. 1. База данных является в основном хранилищем объектов. 2. База данных была разработана для другой системы. 3. База данных разработана для этой системы, но выступает в роли, отличной от хранилища объектов. Если структура базы данных специально проектируется для хранения объектов, то стоит и потерпеть некоторые ограничения в модели ради простоты соответствия объектов. Если нет других требований к структуре базы, ее можно спроектировать так, чтобы легче и эффективнее было поддерживать ее агрегатную целостность в процессе обновления. Структура таблиц реляционной базы данных не обязана отражать модель предметной области. Средства отображения данных сами по себе достаточно богаты возможностями, чтобы сгладить любые существенные отличия. Проблема в том, что иметь много накрадывающихся друг на друга моделей не очень-то удобно и слишком сложно. - Если база данных выступает в основном хранилищем объектов, не позволяйте модели данных и объектной модели «расходиться слишком далеко», вне зависимости от богатства средств отображения. Пожертвуйте частью отношений между объектами ради того, чтобы сделать модель ближе к реляционной. Не бойтесь применять такие формально реляционные стандарты, как нормализация, если это помогает в отображении данных. - Процессы, протекающие вне объектной системы, вообще не должны иметь доступа к хранилищу объектов, потому что они могут нарушить накладываемые объектами инвариантные ограничения. Кроме того, предоставление им права доступа заблокирует модель данных от изменений, и это еще скажется, когда придет время рефакторинга. С другой стороны, во многих случаях данные поступают из устаревшей или внешней системы, которая никогда и не задумывалась как хранилище объектов. В такой ситуации в одной системе фактически соседствуют две модели предметной области. Иногда имеет смысл приспособиться к модели, принятой в другой системе, а иногда, наоборот, - сделать свою модель совершенно другой. Еще одна причина, по которой приходится делать исключения – это быстродействие. Для решения проблем в этой области программисту приходится идти на многие ухищрения. Но для важного и распространенного случая, когда реляционная база данных служит постоянным хранилищем объектов из объектно-ориентированной предметной области, лучше всего применять самый прямой подход. Строка таблицы должна содержать объект - возможно, вместе с его подобъектами в виде агрегата. Внешний ключ в таблице должен транслироваться в ссылку на другой объект-сущность. Если и встречается необходимость иногда отойти от этого прямого подхода, это не должно приводить к полоному забвению принципа прямого соответствия. Привязать объектную и реляционную составляющую к одной и той же модели помогает **единый язык**. Имена и ассоциации элементов в объектах должны до мелочей соответствовать именам и ассоциациям в реляционных таблицах. Хотя в присутствии мощных средств отображения данных это может показаться несущественным, даже небольшие различия в отношениях между данными могут вызвать большую путаницу. Традиция рефакторинга, которая овладела объектно-ориентированным миром, пока не слишком сильно повлияла на проектирование реляционных баз данных. Более того, серьезные проблемы переноса данных делают частые изменения нежелательными. Это может затормозить рефакторинг объектной модели, но если модель базы данных и объектная модель начинают расходиться, то может быстро потеряться прозрачность, наглядность преобразования данных. Наконец, могут быть и причины для введения такой структуры базы данных, которая решительно отличается от объектной модели, пусть даже база специально создавалась именно для данной программной системы. База данных может также использоваться другой программой, в которой вообще не инициализируются экземпляры объектов. Такая база может практически не требовать изменений даже тогда, когда поведение объектов быстро меняется. Тогда возникает искушение углубить разрыв между системой и базой данных. Часто это делается непреднамеренно - разработчикам просто не удается вести базу данных «в ногу» с моделью. Если же такой разрыв выбирается сознательно, в результате вполне может получиться аккуратная и экономная структура таблиц базы, а не корявое нечто, порожденное многочисленными попытками привязать базу данных к самой последней версии объектной модели. ## 11. Стратегическое проектирование ### Bounded Context `Ограниченный контекст` – это явная граница, внутри которой существует модель предметной области, которая отображает `единый язык` в модель программного обеспечения. #### Три принципа - `Контекстность`. Удачная модель, будь она большая или маленькая, должна быть логически непротиворечивой и единообразной, лишенной противоречий и перекрывающихся определений. - `Дистилляция` позволяет сосредоточить внимание на том, что нужно. Стратегическая дистилляция может сделать большую модель понятнее и нагляднее. А это позволяет спроектировать архитектуру смыслового ядра `(core domain)` системы с наибольшей пользой для дела. - `Крупномасштабная структура`. В очень сложной модели легко потерять лес за деревьями. Проблему частично решает дистилляция, но взаимосвязи между ними все равно могут остаться слишком сложными, если отсутствует «главная тема», если не применяются архитектурные формы и `шаблоны` масштаба целой системы. Пример: уровни разделения обязанностей (responsibility layers). `Ограниченные контексты (bounded contexts)` дают возможность выполнять работу одновременно в разных частях системы без повреждения модели или ее нечаянного фрагментирования. Добавление всех этих понятий в единый язык `(ubiquitous language)` группы разработчиков поможет им выработать собственные принципиальные решения. ### `Ограниченные контексты - это не модули` Когда два разных набора объектов явно образуют разные модели, их почти всегда помещают в разные модули. Это позволяет организовать разные `пространства имен` (что существенно для разных контекстов) и создает некоторое разграничение. Но модули также используются и для организации элементов одной модели; они не обязательно выражают намерение разграничить контексты. Отдельные пространства имен, создаваемые модулями внутри ограниченных контекстов, фактически препятствуют обнаружению случайной фрагментации модели. ### Область действия Область действия конкретной модели - замкнутая и ограниченная часть программной системы, внутри которой применяется и может максимально унифицироваться одиночная модель. Модель должна быть самосогласованной. Явно определите контекст, в котором применима модель. Явным образом установите границы в соответствии с организационной структурой группы, особенностями операций в разных частях приложения, организацией баз кода и данных. ## 12. Поддержание целостности модели ### Унификация Сaмыe фундаментальные требования к модели состоят в том, чтобы она была `самосогласованной`, внутренне `непротиворечивой`; чтобы ее термины всегда имели одно и то же значение; и чтобы в модели не было противоречивых правил. Самосогласованность модели - называется `унификацией`. В идеальном мире у нас была бы одна модель, охватывающая всю предметную область, в которой работает корпоративное приложение. Но мир больших систем отнюдь не является идеальным. ### Проблемы Унификации 1. Слишком много уже имеющегося кода придется одновременно заменять на новый. 2. Большие проекты могут затормозиться из-за того, что дополнительная нагрузка по управлению ими превзойдет имеющиеся возможности. 3. Приложениям со специализированными требованиями могут быть навязаны такие модели, которые не полностью соответствуют их задачам, поэтому реализацию важных операций придется выносить куда-то в другое место. 4. И напротив, попытка удовлетворить всех одной моделью добавит в нее столько вариантов и параметров, что ею станет трудно пользоваться. ![image](https://gist.github.com/assets/40522320/c9e9b2dd-a6be-440f-aa88-5ab0ae578cf8) ## Границы Ограниченный контекст `(bounded context)` ставит пределы применимости той или иной модели, чтобы разработчики четко понимали (и разделяли это понимание между собой), в чем следует поддерживать единообразие и согласованность, и как соотносить это с другими контекстами. Интеграция через границы неизбежно потребует какой-то разновидности `трансляции`, и этот вопрос можно будет проанализировать. Расчертить территорию и построить крупномасштабную картину контекстов и связей между ними позволяет карта контекстов `(context мар)`, а еще несколько шаблонов определяют характер различных взаимоотношений между контекстами. Единство же модели внутри ограниченного контекста поддерживается при помощи процесса непрерывной интеграции `(continuous integration)`. ## Распознавание дефектов внутри ограниченного контекста На более тонком уровне верным знаком является непредсказуемое поведение программы. «Выловить» такого рода проблемы позволяет процесс непрерывной интеграции `(continuous integration)` с `автоматизированными тестами`. А вот заблаговременным предупреждением может служить путаница в языке. **При комбинировании элементов** различающихся моделей возникает две категории проблем: `дублирующиеся понятия (концепции)` и `ложные родственники`. #### Дублирование `Дублирование понятий` означает, что в модели есть два элемента (и две соответствующих программных реализации), которые на самом деле представляют одно и то же понятие. #### Ложные родственники `Ложные родственники` встречаются несколько реже, но зато они более коварны. В этом случае двое людей, пользующихся одним и тем же термином (или программным объектом), думают, что имеют в виду одно и то же, а на самом деле это не так. ## Поддержка единства модели Когда в одном и том же ограниченном контексте работает одновременно много людей, модель имеет тенденцию фрагментироваться, распадаться на части. Но разбиение системы на еще меньшие контексты в конце концов приводит к тому, что в ней теряется полезный уровень интеграции и связности. ### Экстремальное программирование Именно в такой среде уместен подход экстремального программирования `(ХР)`. Многие приемы `ХР` нацелены на решение именно специфической проблемы поддержания связной архитектуры при том, что ее постоянно изменяет множество людей. В чистом виде `ХР` - прекрасная методика поддержания целостности модели в пределах одного ограниченного контекста. ![image](https://gist.github.com/assets/40522320/f7872957-e310-44ef-bcbc-2bca8a861677) ### Continuous Integration `Непрерывная интеграция` означает, что вся работа в пределах контекста сливается воедино и приводится в согласованный вид достаточно часто, чтобы даже при возникновении смысловых дефектов они выявлялись и устранялись достаточно быстро. Непрерывная интеграция, как и все остальное в предметно ориентированном проектировании, работает на двух уровнях: - `(1) интеграция понятий модели` - `(2) интеграция программной реализации`. #### Интеграция понятий модели Понятия и концепции интегрируются путем постоянной коммуникации между членами рабочей группы. Самый фундаментальный прием - `постоянная доработка единого языка проекта`. #### Интеграция программной реализации Между тем все написанное программистами интегрируется воедино систематически проводимой процедурой `сборки-компиляции-тестирования`, которая позволяет выявлять дефекты в модели на ранних стадиях. Для интеграции проекта используется много процедур, но большинство самых эффективных обладает следующими характеристиками: - пошаговая, легко воспроизводимая технология сборки и компиляции; - наличие наборов автоматизированных тестов; - правила, определяющие временные рамки (достаточно непродолжительные) для существования не интегрированных в общую систему изменений. ### Концептуальная интеграция Другой стороной медали в эффективных процедурах интеграции является `концептуальная интеграция`, хотя ее не часто определяют формально: - постоянная практика в едином языке проекта при обсуждении модели и приложения. ### Условия применения Непрерывная интеграция `(continuous integration)` применима внутри любого отдельного ограниченного контекста `(bounded context)`, превосходящего по размеру работу `двух человек`. Она нацелена на поддержание целостности одиночной модели этого контекста. ## Карта контекстов (Context Map) ![image](https://gist.github.com/assets/40522320/f4efc5d1-afd7-4ceb-9756-cfcf121d2534) Следуя подходу DDD, определенная команда должна создать собственную `карту`, которая отражает пространство решений, в которой находится эта команда. Эта `карта` состоит из `ограниченных контекстов`, а также интеграционных связей между ними. > Если между разными контекстами необходимо наличие связей, эти контексты имеют тенденцию `«просачиваться»` друг в друга. Интеграция функциональности и данных должна выполняться через `трансляцию.` **Определите все модели, используемые в проекте, и задайте для каждой свой ограниченный контекст. Учитывайте и неявные модели подсистем, не являющихся объектно-ориентированными. Дайте каждому ограниченному контексты имя и включите эти имена в единый язык проекта. Опишите точки соприкосновения между моделями, явно задавая трансляцию для любого способа коммуникации и выделяя любые совместно используемые ресурсы. Постройте карту уже существующей территории. Преобразованиями займетесь потом.** ## Тестирование в границах контекста Особое значение имеет `тестирование точек соприкосновения между ограниченными контекстами`. Тесты помогают прояснить тонкости трансляции и компенсировать ухудшение коммуникации, которое обычно происходит на границе. Тестирование может служить системой раннего предупреждения. Здесь следует соблюсти только два важных принципа. 1. `Ограниченные контексты должны иметь имена`, чтобы на них можно было ссылаться при обсуждении. Эти имена должны войти в единый язык группы разработчиков. 2. Все разработчики должны знать, `где пролегают границы`, и уметь `распознать контекст любого фрагмента кода` в любой ситуации. # Взаимосвязи между ограниченными контекстами В рассматриваемых далее шаблонах предлагаются подходы и методики для установления взаимосвязей между моделями. Эти шаблоны выполняют сразу две задачи: - задают цели для успешной организации процесса разработки - предлагают словарь для описания той организации, которая уже существует в настоящее время. ## 1) Общее ядро ![image](https://gist.github.com/assets/40522320/4c02ee40-dfbe-4bdc-9b57-db74e1e4a772) `Общее ядро` (Shared kernel). Общая часть модели и кода образует тесную взаимосвязь. Обозначается четкая граница подмножества модели предметной области, которую команды согласны считать общей. Ядро должно быть маленьким. Оно не может изменяться без консультации с другой командой . Необходимо согласовывать `единый язык` команд. Наборы автоматизированных тестов следует интегрировать в одно целое, потому что при внесении изменений должны выполняться все тесты обеих групп. `Общее ядро (shared kernel)` часто представляет собой смысловое ядро предметной области `(core domain)`, набор естественных подобластей `(generic subdomains)` или то и другое одновременно. Но в принципе оно может представлять собой любую часть модели, которая требуется каждой из двух рабочих групп. Здесь ставится цель уменьшить дублирование работы и сделать интеграцию между двумя подсистемами сравнительно простой. ## 2) Группы «заказчик-поставщик» `Разработка заказчки-поставщик` (Customer-supplier development). Когда две команды находятся в отношении «нижестоящий и вышестоящий», и команды вышестоящие учитывают приоритеты нижестоящих команд. - Часто бывает так, что одна подсистема фактически подает что-то на вход другой, снабжает ее данными. `«Нижний»` компонент выполняет анализ или другие функции, которые мало что передают в `«верхний»` компонент, так что все зависимости и взаимосвязи идут `в одном направлении`. - Верхняя и нижняя подсистемы естественным образом разделяются на `два` различных ограниченных контекста `(bounded contexts)`. Трансляция в одну сторону проще, чем в обе. - Определите четкие отношения `«заказчик-поставщик»` между двумя группами. На сеансах планирования нижняя группа должна играть роль заказчика по отношению к верхней. - Совместно разработайте `автоматизированные приемочные тесты` для проверки ожидаемого интерфейса. Добавьте эти тесты в набор тестов верхней рабочей группы, чтобы они выполнялись в рамках происходящей у них непрерывной интеграции. Такое тестирование позволит верхней группе спокойно вносить изменения, не боясь побочных эффектов внизу. ### В этом стратегическом шаблоне есть два ключевых момента. 1. Отношения заказчика и поставщика подразумевают, что потребности заказчика однозначно стоят на первом месте. Но поскольку нижняя группа разработчиков не является единственным заказчиком, требования всех заказчиков приходится согласовывать в процессе переговоров. 2. Должен существовать набор автоматизированных тестов, который позволяет верхней группе вносить изменения в код, не боясь что-нибудь испортить в работе нижней, а также дает возможность нижней группе сосредоточиться на своих задачах, а не постоянно контролировать деятельность верхней. ## 3) Конформист Когда две группы разработчиков с отношениями типа `«верх-низ»` не имеют эффективного руководства из одного источника, такой типовой образец сотрудничества, как заказчик и поставщик, неприменим. Когда две группы разработчиков состоят в отношениях `«верх-низ»` и у `«верха»` нет мотивации удовлетворять потребности `«низа»`, нижняя группа оказывается беспомощной. Нижестоящая команда учитывает сложность трансляции между ограниченными контекстами, беспрекословно подчиняясь модели вышестоящей команды. Из альтруизма разработчики верхней группы могут выдавать обещания, но они вряд ли будут выполнены. Вера в добрые намерения побуждает нижнюю группу строить планы на основе обещанных ресурсов, которые никогда не поступят. Проект внизу будет заморожен до тех пор, пока группа не научится обходиться тем, что у нее есть. ### Три выхода Из этой ситуации есть три возможных выхода. #### Первый - Separate Ways Один - это вообще отказаться от помощи сверху. Иногда мы преувеличиваем ценность или недооцениваем цену такой зависимости. Если нижняя группа решает перерезать свой поводок, то обе группы начинают вести отдельное существование `(separate ways)`. > Подробнее далее в пункте (separate ways) #### Второй - conformist Иногда ценность использования программ сверху настолько велика, что зависимость от них приходится поддерживать. В этом случае остаются два выхода. Если с ней очень трудно работать, например, из-за недостаточной инкапсуляции, неудобного абстрагирования или моделирования в парадигме, которую другая группа не может использовать, то нижним разработчикам все равно придется строить свою собственную модель. ![image](https://gist.github.com/assets/40522320/c9ca7651-e147-4076-8285-c4d90dc67b68) Устраните сложность трансляции между ограниченными контекстами `(bounded contexts)`, придерживаясь модели верхней группы разработчиков. Действия в духе конформизма позволяют очень сильно упростить интеграцию. К тому же с группой-поставщиком можно будет разговаривать на едином языке. Управляет-то процессом именно поставщик, поэтому есть смысл сделать общение удобным для него. #### Третий - Anticorruption layer Такое решение углубляет зависимость от верхней группы и накладывает ограничения на приложение в виде возможностей только верхней модели. Если такие компромиссы неприемлемы, но зависимость от верхней группы неизбежна, то еще остается второй вариант: изолировать себя, насколько это возможно, с помощью предохранительного уровня `(anticorruption layer)`. > Подробнее далее в пункте Предохранительный уровень ### Отличия от Ядра Разница между шаблонами состоит в процессе принятия решений и ходе разработки. Если общее ядро - это `сотрудничество` между двумя тесно скоординированными группами, то конформист представляет интеграцию с группой, которая фактически `не заинтересована в сотрудничестве`. ## 4) Предохранительный уровень (Anticorruption layer) `Предохранительный уровень` (Anticorruption layer). Если управление и коммуникация не соответствуют общему ядру, партнеру, или отношению «Заказчик-поставщик», то трансляция является сложной. Нижестоящий клиент должен создать изолирующий слой, чтобы обеспечить свою систему вышестоящей системы в терминах своей модели предметной области. Этот уровень общается с другой системой с помощью существующего интерфейса, не требуя или почти не требуя модификаций другой системы. Внутри уровня будет идти необходимая трансляция в обе стороны между двумя моделями ### Проектирование интерфейса предохранительного уровня Открытый интерфейс предохранительного уровня `(anticorruption layer)` обычно построен как набор служб `(services)`, хотя иногда он может принимать вид объекта-сущности `(entity)`. ### Реализация предохранительного уровня Один из способов организации архитектуры предохранительного уровня `(anticorruption layer)` - представить его в виде набора фасадных объектов `(facades)`, адаптеров `(adapters)` и трансляторов, а также механизмов коммуникации и переноса, обычно необходимых для связи между системами. ### `Фасадный объект` `Фасадный объект` - это альтернативный интерфейс некоторой подсистемы, который упрощает обращение к ней со стороны клиента и вообще облегчает ее использование. Нам известно в точности, какие функции другой системы мы хотим использовать, вот мы и пишем фасадный объект, который упрощает и спрямляет доступ к ним, скрывая все остальное. Фасадный объект не изменяет модель представляемой им системы. Он должен быть написан в строгом соответствии с ее моделью, иначе мы в лучшем случае разбросаем ответственность за трансляцию на много объектов и перегрузим сам фасадный объект, а в худшем - построим еще одну модель, которая не относится ни к другой системе, ни к нашему собственному ограниченному контексту. Между тем фасадный объект относится к ограниченному контексту другой системы. Это ее представитель, дружественно настроенный и специально приспособленный к нашим потребностям. ### `Адаптер` `Адаптер (adapter)` представляет собой объект-оболочку, позволяющую клиенту пользоваться не тем протоколом, который понятен реализатору операции, а каким-нибудь другим. Когда клиент посылает сообщение адаптеру, оно преобразуется в семантически эквивалентное сообщение и пересылается «адаптанту». Ответ оттуда опять преобразуется и пересылается назад. Термин «адаптер» (что по сути означает «переходник») используется здесь несколько произвольно. Мы тут выбираем пользоваться адаптированным интерфейсом, и при этом не исключаем, что «адаптант» - вообще не объект. Наша цель - организовать трансляцию между двумя моделями, но «дух» адаптера при этом сохраняется. Для каждой определяемой нами службы `(service)` необходим адаптер, который поддерживает интерфейс этой службы и умеет делать эквивалентные запросы к другой системе или ее фасадному объекту. ### `Транслятор` Остается еще такой элемент, как `транслятор`. Задача адаптера состоит в том, чтобы делать запросы. Фактическое преобразование концептуальных объектов или данных - это совсем другая сложная задача, которую можно передать отдельному объекту, отчего обе задачи станут понятнее. Транслятор может быть небольшим объектом, создаваемым и инициализируемым по необходимости. Ему не нужно собственное состояние, и его не надо распределять, поскольку он должен находиться там же, где и обслуживаемый(-е) им адаптер(ы). ![image](https://gist.github.com/assets/40522320/79225414-4d01-4490-b583-f7ea282526a3) ## 5) Отдельное существование (Separate ways) `Отдельное существование` (Separate ways). Если между двумя наборами функциональных возможностей нет важного отношения, их можно полностью отсоединить друг от друга. Интеграция всегда дорого стоит, а выгоды бывают незначительны. ## 6) Службы с открытым протоколом (Open host service) `Служба с открытым протоколом` (Open host service). Определяется протокол, который предоставляет доступ к системе как к набору служб. Для учета новых требований интеграции этот протокол расширяется и уточняется. Если подсистему необходимо интегрировать со многими другими, необходимость построения отдельного транслятора для каждой из других систем становится тяжким бременем. Все больше компонентов приходится дорабатывать, а потом беспокоиться о возрастающем количестве изменений. Такая формализация коммуникации подразумевает, что у разных моделей имеется некий `общий словарь` - основа для интерфейсов соответствующих служб. В результате сторонние подсистемы оказываются привязанными к модели с открытым протоколом `(open host)`, и это вынуждает их разработчиков изучать конкретный диалект, используемый группой разработки протокола. В некоторых случаях снизить зависимость и облегчить взаимопонимание можно, если применить в качестве промежуточной модели достаточно известный и общедоступный язык `(published language)` ## 7) Общедоступный язык (published language) `Общедоступный язык` (Published language). Трансляция между моделями двух `ограниченных контекстов` требует общего языка. В качестве среды для коммуникации используется хорошо документированный общий язык, который может выразить необходимую информацию о предметной области, выполняя при необходимости перевод информации с другого языка на этот. ## 8) Партнерство `Равные отношения взаимовлияния` — две группы разработчиков занимаются отдельно своими ограниченными контекстами, прибегая к взаимодействию с целью достижения общей цели. В таком взаимодействии успеха или провала достигают обе группы. ## Преобразование границ Преимущества больших контекстов: - различные пользовательские операции связаны между собой более плавно и естественно, когда как можно больше разных объектов и операций охвачены одной унифицированной моделью; - легче понять одну связную модель, чем две разные и уровень отображения между ними; - трансляция между двумя моделями может оказаться сложной (а то и невозможной); - общий язык стимулирует четкое взаимодействие в группе разработчиков. Преимущества малых контекстов: - снижаются издержки коммуникации между разработчиками; - непрерывную интеграцию (continuous integration) проще выполнять в малых группах с небольшими базами кода; - в больших контекстах могут потребоваться более универсальные и абстрактные модели, требующие редко встречающейся квалификации разработчиков; - небольшие модели могут обслуживать специальные потребности или соответствовать жаргону специализированных групп пользователей, а также узкоспециальных диалектов единого языка (ubiquitous language). ## Проектируемая система Под проектируемой системой подразумевается то программное обеспечение, которое фактически разрабатывает ваша группа. В этих пределах можно определить несколько ограниченных контекстов и внутри каждого выполнять непрерывную интеграцию, чтобы поддерживать их унификацию. ### Пример: Можно поступить просто: ввести один ограниченный контекст для всей проектируемой системы. Это хороший вариант, например, для группы менее чем из десяти человек, которая работает над тесно связанным набором функций. По мере роста численности разработчиков непрерывная интеграция (continuous integration) становится все труднее. Может быть, стоит подумать об общем ядре (shared kernel) и вынести относительно независимые наборы функций в отдельные контексты, чтобы над каждым работало не более десяти человек. Если же все взаимоотношения между двумя такими группами направлены в одну сторону, можно организовать отношения по типу групп «заказчик-поставщик» (customer/supplier development teams). ## Преобразование > ## Слияние контекстов: от отдельного существования к общему ядру > 1. Оцените начальную ситуацию. Убедитесь, что два контекста действительно едины по смыслу, прежде чем начинать их формальную унификацию. > 2. Подготовьте процесс. Необходимо выработать порядок работы с общим кодом и установить правила именования модулей. Код общего ядра должен подвергаться интеграции как минимум раз в неделю. Для него должен существовать набор тестов. Создайте его еще до того, как писать общий код. > 3. Выберите для начала небольшую подобласть нечто дублирующиеся в обоих контекстах, но не входящее в смысловое ядро (core domain) предметной области. Первое слияние имеет своей целью только «обкатать» процедуру, так что для него лучше выбрать нечто простое, сравнительно отдельное и некритичное. Изучите уже существующие интеграции и трансляции. Выбор чего-то уже транслируемого хорош тем, что транслируемость проверена. А по итогам работы трансляционный уровень еще уменьшится в размерах. > 4. Сформировать группу из двух-трех разработчиков из обеих групп, для выработки общей модели подобласти. Независимо от того, каково происхождение модели, она должна быть проработана во всех подробностях. Это означает, в том числе, определение синонимов и установку соответствия между терминами, которые еще не транслировались. Объединенная группа набросает и ориентировочный набор тестов для модели. > 5. Разработчики из каждой группы берутся реализовать модель (или адаптировать существующий код, поступающий в совместное пользование), проработать детали и довести до рабочего состояния. Если они сталкиваются с проблемами в модели, то снова собирают группу (см. п. 4) и сами участвуют в необходимой доработке понятий. > 6. Разработчики каждой из групп берутся за задачу интегрирования с новым общим ядром (shared kernel). > 7. Убрать трансляции, которые больше не нужны. > > ## От открытого протокола к общедоступному языку. > Вы интегрируетесь с другими системами через набор открытых протоколов (как open-host services), но по мере того, как новые системы запрашивают доступ к вашей, бремя ее сопровождения становится все тяжелее, и понять все взаимодействия становится все труднее. Необходимо формализовать отношения между системами через общедоступный язык (published language). > 1. Если в отрасли есть единый стандартный язык обмена данными, оцените его возможности и используйте его, если это допустимо. > 2. Если никакого общего стандарта или опубликованного языка нет, начните с четкого выделения смыслового ядра (core domain) системы, которая будет служить сервером открытого протокола. > 3. Используйте смысловое ядро как основу для языка коммуникации, подключив стандартную парадигму обмена данными наподобие XML, если это возможно. > 4. Сделайте новый язык общедоступным (опубликуйте его) как минимум для всех, кто с вами взаимодействует. > 5. Если важна архитектура новой системы, опубликуйте и ее тоже. > 6. Постройте трансляционные уровни для каждой подключающейся к вам системы. > 7. Переключитесь на новую систему. ## 13. Дистилляция `Дистилляция` — деятельность направленная на первоначально получение информации о самых важных частях системы, которая только готовится к проектированию. ### Цели Стратегическая дистилляция модели предметной области выполняет сразу несколько функций. 1. Помогает всем разработчикам понять архитектуру системы в целом и взаимосвязь между ее частями. 2. Облегчает процесс коммуникации, выделяя ключевую модель обозримой величины, которая включается в единый язык (ubiquitous language). 3. Задает направление рефакторинга. 4. Фокусирует внимание на тех частях модели, которые имеют наибольшую ценность. 5. Помогает определиться с субподрядами (аутсорсингом), использованием готовых компонентов, распределением задач. ![image](https://gist.github.com/assets/40522320/962c1772-34a1-4881-b0b2-1e6f371db7b3) ### Пример ![](https://habrastorage.org/r/w780/files/832/98f/a23/83298fa239a64ad08ae497ddd4595dc2.jpg) ## Смысловое ядро (Core Domain) Части модели, основные для выполнения главной задачи приложения. Следует подключить к работе над этой частью системы наиболее квалифицированных специалистов, с тем чтобы построить углубленную модель и гибкую архитектуру. ## Неспециализированные подобласти (Generic Subdomains) Части модели, которые по отношению к задаче и специализации модели являются вторичными или вспомогательными. Эти части не приносят непосредственной пользы `смыслового ядра`, и поэтому, следует минимально тратить силы ведущих разработчиков на этих частях модели, в пользу `смыслового ядра`. Сами `неспециализированные подобласти` следует посредством рефакторинга вынести в отдельные модули. ## Введение в предметную область (Domain Vision Statement) Краткое описание `смыслового ядра`, описывающие в очень общих чертах различные компоненты будущей систем, так что в описание включалось только то, что однозначно отличает каждую из моделей. Задаёт общее направление работы разработчиков и т.к. не содержит деталей, подходит только для программистов слаженно работающих друг с другом. ## Схематическое ядро (Highlighted Core) Описание `смыслового ядра` в более подробном виде, чем `введение в предметную область`, необходимое в том случае, когда коммуникация в команде не на высоком уровне. Эванс предлагает два варианта. ### Дистилляционный документ Первый — `дистилляционный документ` — краткий документ в несколько страниц, с описанием `смыслового ядра` и основных взаимодействий между элементами ядра. Документ может давать представление о самых фундаментальных взаимодействиях в модели на примерах или на абстрактном уровне. В нем могут использоваться UML-диаграммы классов или последовательности операций, нестандартные диаграммы, характерные для предметной области, тщательно сформулированные текстовые описания, а также сочетания всего перечисленного. **Дистилляционный документ - это не полный проектный документ. Это минимально необходимое вступление, кратко очерчивающее ядро и располагающее к более подробному исследованию тех или иных его частей.** ### Разметка ядра Второй — `разметка ядра` — перечисление основных элементов ядра, без разъяснения их роли. ## Связанные механизмы (Cohesive Mechanisms) Алгоритмически сложная часть системы, вынесенная за `интуитивные интерфейсы` в отдельные модули. Такое сокрытие алгоритма позволяет глубже концентрироваться на модели, нежели на том как её обрабатывать. Выделите концептуально связный механизм `(cohesive mechanism)` в отдельную небольшую программную среду или библиотеку. Особое внимание окажите формализациям или хорошо документированным категориям алгоритмов. Функциональные возможности среды покажите с помощью информативного интерфейса `(intention-revealing interface )`. После этого другие элементы предметной области можно будет сосредоточить на выражении основной задачи `(«что делать»)`, а тонкости ее решения («как делать»)` передать новой среде. ### Сравнение связных механизмов и неспециализированных подобластей Как неспециализированные подобласти `(generic subdomains)`, так и связные механизмы `(cohesive mechanisms)` вводятся тогда, когда есть необходимость разгрузить смысловое ядро `(core domain).` Разница состоит в принимаемых ими на себя обязанностях. Неспециализированная подобласть основана на смысловой модели, которая представляет определенные аспекты взгляда разработчиков на общую проблему. В этом она принципиально не отличается от смыслового ядра, только является менее центральной, важной и узкоспециализированной. Связный механизм же никоим образом не представляет предметную область; он решает некую частную вычислительную задачу, поставленную смысловыми моделями. ## Декларативный стиль (Declarative Style) `Смысловое ядро` дистиллированное до такой степени, что может использоваться как декларативная архитектура, приобретая вид доменно-ориентированного языка. ## Выделенное ядро (Segregated Core) Несколько итераций рефакторинга `смыслового ядра`, направленные на Low Coupling и High Cohesion. В результате появляется чётко выделенное ядро и отдельно связанные механизмы и неспециализированные подобласти. ### Рефакторинг Обычно рефакторинг, порождающий выделенное ядро, состоит из следующих этапов: 1. Определяется подобласть смыслового ядра (иногда ее можно взять из дистилляционного документа). 2. Имеющие к ней отношение классы перемещаются в новый модуль под именем, соответствующим основному понятию, которое их объединяет. 3. Выполняется рефакторинг кода, при котором «отрезаются» данные и функциональные возможности, не выражающие это понятие непосредственно. Удаленные аспекты выносятся в классы (возможно, новые) других пакетов. Можно постараться объединить их с концептуально родственными задачами, но не тратить слишком много времени на поиск идеального решения. Основное внимание следует уделить очистке подобласти ядра, а также сделать ссылки из него на другие пакеты явными и наглядными. 4. Выполняется рефакторинг нового модуля выделенного ядра с целью упрощения и повышения информативности взаимосвязей и взаимодействий в нем, а также для минимизации и прояснения его взаимосвязей с другими модулями. (Это становится постоянной целью рефакторинга.) 5. Вышеописанное повторяется с другой подобластью ядра, и так до тех пор, пока выделенное ядро не будет построено. ## Абстрактное ядро (Abstract Core) Отдельный модуль `смыслового ядра`, который получается в процессе рефакторинга кода с исключением взаимозависимостей и выделением общих абстрактных частей модели. `Абстрактное ядро` даёт представление об основных понятиях и взаимодействиях, а специализированные модули ссылаются на него. ### Для чего нужно? Если между подобластями, находящимися в отдельных модулях, происходит интенсивное взаимодействие, то либо приходится создавать много ссылок между ними, что частично лишает смысла само модульное разбиение, либо организовывать косвенные способы взаимодействия, что ухудшает наглядность модели. Определите наиболее фундаментальные понятия в модели и выделите их рефакторингом в отдельные классы, абстрактные классы или интерфейсы. Спроектируйте эту абстрактную модель так, чтобы она выражала большую часть взаимодействий между существенными компонентами. Поместите эту абстрактную модель в собственный модуль, оставив более специализированные классы конкретных программных реализаций в их собственных модулях, определяемых подобластью. Большинство специализированных классов теперь будет ссылаться на модуль абстрактного ядра, но не на другие специализированные модули. Абстрактное ядро (abstract core) дает самое сжатое представление основных понятий и взаимодействий между ними. ## Итог Применив дистилляцию, разработчики получают возможность быстрее распознать предмет работы, в котором как раз и появляется добавленная стоимость. Дистиллированное смысловое ядро отражает то, с каким вниманием и важностью необходимо проводить работы над системой. Комбинируя дистилляцию с картой контекста, можно правильно распределить усилия по разработке системы. ## 14. Крупномасштабная структура Крупномасштабная структура носит необязательный характер и служит ориентиром разработчикам, инструментом для большего понимания системы. `«Крупномасштабная структура»` - это своего рода язык, на котором можно обсуждать и описывать систему грубыми штрихами. Разработайте схему правил или ролей/взаимоотношений, распространяющуюся на всю систему и позволяющую понять место каждой части в едином целом - пусть даже без подробного знания обязанностей всех этих частей. ![image](https://gist.github.com/assets/40522320/83ca1736-4554-4c60-a8ca-6e3071ac7727) ## Метафорический образ системы (System Metaphor) Образ системы (метафора), который позволяет разработчикам и предметным экспертам улучшить согласованность между отдельными элементами системы и **ограниченными контекстами**. ## Уровни разделения обязанностей (Responcibility Layers) Меры по снижению зацепления элементов и повышению связности путём расслоения системы по функциональному назначению. Это в свою очередь позволяет задать порядок и направление взаимодействия между слоями. Выбирая уровни следует исходить из _информативности, концептуальной взаимосвязанности, наличия концептуальных контуров_. ## Уровень знаний (Knowlage Level) Модели могут содержать большую вариабельность, которая квалифицируется ролями или взаимоотношениями между сервисами. Отличается от **уровней разделения обязанностей** двунаправленным взаимодействием слоёв. ## Среда подключаемых компонентов (Pluggable Component Framework) В случае проработанной углубленной модели, наличии **абстрактного ядра**, система может различные предметные модули могут работать с абстрактным ядром через одни и те же интерфейсы или через применение **общедоступного языка**. ## Эволюционная организация (Evolving Order) Подход, при котором модели и их определения не являются излишне жёсткими, позволяя по мере развития проекта, получения углубленной модели, изменяться архитектуре значительным образом. Применяется при недостаточно детализированных знаниях о предметной области. ## 15. Event Storming ### Что такое Event Storming Это метод моделирования бизнес-процессов в области разработки программного обеспечения. Он представляет собой совместную и интерактивную деятельность, в которой разработчики, бизнес-аналитики и эксперты вместе анализируют бизнес-логику и взаимодействие событий в системе. Основные шаги: - Пригласите нужных людей на семинар. В идеале нужна группа 6–8 человек, в которой будет правильное сочетание тех, кто знает, какие вопросы задавать (и кому интересно послушать ответ), и тех, кто знает ответы. - Обеспечьте неограниченное пространство для моделирования. - Исследуйте домен, начиная с событий домена. Событие домена — это что-то значимое, произошедшее в домене. - Исследуйте происхождение доменных событий - Ищите агрегаты Также эти шаги можно дополнить: - Изучение поддоменов - Изучение ограниченных контекстов - Наброски персонажей пользователей - Наброски ключевых приемочных тестов - Набросок ключевых артефактов модели чтения Единого формата воркшопа нет. На самом деле первые шаги более или менее одинаковы, но формат может варьироваться в зависимости от ролей участников и масштаба проекта. ## Основные принципы ### Требования Для воркшопа по Event Storming важно, чтобы на нем присутствовали нужные люди. Сюда входят люди, которые знают, какие вопросы задавать (обычно разработчики), и те, кто знает ответы (эксперты в предметной области, владельцы продуктов). ### Шаги 1. Создайте доменные события (оранж. стикер). 2. Добавьте команды, вызвавшие событие домена (синий стикер). 3. Добавьте актера, который выполняет команду (опционально) (желтый). 4. Добавьте соответствующий агрегат (тож желтый). Также еще есть внешние системы (розовый стикер) и UI (зеленый стикер). ## Проведение Рассмотрим классический, достаточно эффективный способ проведения Event Storming, разбитый на итерации: ### Итерация №1 Цель: выявить максимальное количество событий. 1. Привести простой пример, например на небольшом магазине или сервисе заказа такси; 2. Рассказать теорию о событиях: 1. Что-то, что произошло в прошлом; 2. Всегда в прошедшем времени («Клиент оплатил товар»); 3. Практика: 1. Разбиться на пары или тройки и внутри группы выписать все события, происходящие в системе или бизнесе; 2. Поочередно разместить события на временной шкале; 3. Проиграть вслух события вперед и назад; ### Итерация №2 Цель: выявить команды, действующих лиц, внешние системы и модели чтения 1. Привести простой пример; 2. Рассказать теорию о Командах, Действующих лицах, внешних системах и модели чтения; 3. Практика: 1. Перемешаться, в каждой группе должен быть как минимум один эксперт в предметной области; 2. Сразу на стене размещать Команды, Действующих лиц, внешние системы и модели чтения; 3. Каждая команда проигрывает процесс остальным. Выявляется еще больше пробелов. Отлавливаем предположения и заполняем пропущенное. ### Итерация №3 Цель: выявить правила и процедуры. Они заполняют собой пустоты между Command и Event. Можно задавать вопросы вида «Это происходит всегда?» или «Это Событие всегда следует за этой Командой?». 1. Привести простой пример; 2. Рассказать теорию о Policy и Procedures; 3. Практика: 1. Размещаем на стене Policy и Procedures; 2. Проигрываем события вперед и назад. ### Итерация №4 Цель: выявить агрегаты. 1. Привести простой пример 2. Рассказать теорию об Агрегатах 3. Практика 1. Попарное выделение агрегатов 2. Проигрываем события вперед и назад. Агрегаты представляют собой сущности, которые группируют взаимосвязанные объекты и данные, и они обычно представляют собой самостоятельные единицы, которые могут быть изменены или модифицированы. ![Acumalaka GIF - Acumalaka GIFs](https://media.tenor.com/7KcfCKuMtjgAAAAM/acumalaka.gif)