Академический Документы
Профессиональный Документы
Культура Документы
узнать как можно больше, как можно быстрее. Поэтому я стал немного увлекаться
экспериментами, много читать и писать, уделяя особое внимание дизайну и архитектуре
программного обеспечения. Вот почему я пишу эти статьи, чтобы помочь себе в
обучении.
+27 71K 336 14 +14
Поток управления
Начнём с того, что вспомним архитектуры EBI и Ports & Adapters. Обе они явно
разделяют внутренний и внешний код приложения, а также адаптеры для соединения
внутреннего и внешнего кода.
Кроме того, архитектура Ports & Adapters явно определяет три фундаментальных блока
кода в системе:
Ядро приложения — самое главное, о чём нужно думать. Этот код позволяет совершать
реальные действия в системе, то есть это и ЕСТЬ наше приложение. С ним могут
https://habr.com/ru/post/427739/ 3/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Инструменты
Вдали от самого важного кода ядра есть ещё инструменты, которые использует
приложение. Например, ядро БД, поисковая система, веб-сервер и консоль CLI (хотя
последние два также являются механизмами доставки).
https://habr.com/ru/post/427739/ 4/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Кажется странным отнести консоль CLI в тот же тематический раздел, что и СУБД, ведь у
них разное предназначение. Но фактически и то, и другое — инструменты,
используемыми приложением. Ключевое различие в том, что консоль CLI и веб-сервер
говорят приложению что-то сделать, ядро СУБД, наоборот, получает команды от
приложения. Это очень важное различие, поскольку оно сильно влияет на то, как мы
пишем код для соединения этих инструментов с ядром приложения.
Подключение инструментов и механизмов доставки к ядру приложения
Кроме того, порт может быть шиной команд (command bus) или интерфейсом шины
запросов (query bus). В этом случае конкретная реализация шины команд или запросов
вводится в контроллер, который затем создает команду или запрос и передаёт его
соответствующей шине.
Вторичные или управляемые адаптеры
адаптеров. Эти уровни предназначены для того, чтобы внести некоторый порядок в
бизнес-логику, внутреннюю часть «шестиугольника» портов и адаптеров. Как и раньше,
направление зависимостей — к центру.
Уровень приложения (прикладной уровень)
Варианты использования — это процессы, которые можно запустить в ядре одним или
несколькими пользовательскими интерфейсами. Например, в CMS может быть один UI
для обычных пользователей, другой независимый UI для администраторов CMS, ещё
один интерфейс CLI и веб-API. Эти UI (приложения) могут инициировать уникальные или
общие варианты использования.
Варианты использования определяются на прикладном уровне — первом уровне DDD и
архитектуры Onion.
Далее внутри есть уровень домена. Объекты на этом уровне содержат данные и логику
для управления этими данными, которые являются специфическими для самого домена
и не зависят от бизнес-процессов, запускающих эту логику. Они независимы и
совершенно не знают о прикладном уровне.
https://habr.com/ru/post/427739/ 10/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Сервисы домена
прикладном уровне, таких как службы приложений или репозитории. С другой стороны,
она может использовать другие доменные службы и, конечно же, объекты модели
домена.
Модель домена
До сих пор мы изолировали код по слоям, но это слишком подробная изоляция кода. Не
менее важно посмотреть на картину более общим взглядом. Речь идёт о разделении
кода по поддоменам и связанным контекстам в соответствии с идеями Роберта
Мартина, выраженными в screaming-архитектуре [то есть архитектура должна
«кричать» о самом приложении, а не о том, какие фреймворки в нём используются —
прим. пер.]. Здесь говорят об организации пакетов по функциям или по компонентам, в
не по слоям, и это довольно хорошо объяснил Саймон Браун в статье «Пакеты по
компонентам и тестирование в соответствии с архитектурой» в своём блоге:
https://habr.com/ru/post/427739/ 12/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Эти разделы кода сквозные для всех слоёв, описанных ранее, и это компоненты нашего
приложения. Примеры компонентов — биллинг, пользователь, проверка или учётная
запись, но они всегда связаны с доменом. Ограниченные контексты, такие как
авторизация и/или аутентификация, должны рассматриваться как внешние инструменты,
для которых мы создаём адаптер и прячемся за каким-то портом.
https://habr.com/ru/post/427739/ 13/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Разъединение компонентов
Так же, как в мелкозернистых единицах кода (классы, интерфейсы, трейты, миксины и
проч.), крупные единицы (компоненты) выигрывают от слабого сцепления и плотной
связности.
Чтобы разъединить классы, мы используем инъекцию зависимостей, вводя зависимости
в класс, а не создавая их внутри класса, а также инверсию зависимостей, делая класс
зависимым от абстракций (интерфейсов и/или абстрактных классов) вместо конкретных
классов. Это означает, что зависимый класс ничего не знает о конкретном классе,
который будет использовать, у него нет ссылки на полное имя классов, от которых он
зависит.
Точно так же в полностью разъединенных компонентах каждый компонент ничего не
знает ни о каком другом компоненте. Другими словами, у него нет ссылки на какой-либо
мелкозернистый блок кода из другого компонента, даже на интерфейс! Это означает,
что инъекции зависимостей и инверсии зависимостей недостаточно для разделения
компонентов, нам понадобятся какие-то архитектурные конструкции. Могут
понадобиться события, общее ядро, согласованность в конечном счёте (eventual
consistency) и даже служба обнаружения сервисов (discovery service)!
https://habr.com/ru/post/427739/ 14/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Когда один из наших компонентов (компонент B) должен что-то делать всякий раз, когда
что-то ещё происходит в другом компоненте (компонент A), мы не можем просто
сделать прямой вызов из компонента A в класс/метод компонента B, потому что тогда A
будет связан с B.
Однако мы можем использовать диспетчер событий для отправки события приложения,
которое будет доставлено любому компоненту, прослушивающему его, включая B, и
прослушиватель событий в B вызовет желаемое действие. Это означает, что компонент
A будет зависеть от диспетчера событий, но будет отделён от компонента B.
Тем не менее, если само событие «живёт» в A, это означает, что B знает о
существовании А и связан с ним. Чтобы удалить эту зависимость, мы можем создать
библиотеку с набором функциональных возможностей ядра приложения, которые будут
совместно использоваться всеми компонентами — общее ядро. Это означает, что оба
компонента будут зависеть от общего ядра, но будут отделены друг от друга. Общее
ядро содержит функциональные возможности, такие как события приложения и домена,
но оно также может содержать объекты спецификации и всё, что имеет смысл
совместно использовать. При этом оно должно быть минимального размера, поскольку
любые изменения в общем ядре повлияют на все компоненты приложения. Кроме того,
если у нас есть polyglot-система, скажем, экосистема микросервисов на разных языках,
то общее ядро не должно зависеть от языка, чтобы его понимали все компоненты.
Например, вместо общего ядра с классом событий оно будет содержать описание
события (то есть имя, свойства, возможно, даже методы, хотя они были бы более
https://habr.com/ru/post/427739/ 15/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Как я уже сказал выше, поток управления идёт от пользователя в ядро приложения, к
инструментам инфраструктуры, затем опять в ядро приложения — и обратно к
пользователю. Но как именно классы работают вместе? Кто от кого зависит? Как мы их
составляем?
Как Дядя Боб в своей статье о «чистой» архитектуре (Clean Architecture), я постараюсь
объяснить поток управления схемами UMLish…
Без шины команд/запросов
https://habr.com/ru/post/427739/ 17/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
C шиной команд/запросов
https://habr.com/ru/post/427739/ 19/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Вы могли заметить, что нет никаких зависимостей между шиной, командой, запросом и
обработчиками. По сути, они не должны знать друг о друге, чтобы обеспечить хорошее
разъединение. Способ направления шины на конкретный обработчик для обработки
команды или запроса настраивается в простой конфигурации.
В обоих случаях все стрелки — зависимости, которые пересекают границу ядра
приложения — указывают внутрь. Как объяснялось ранее, это фундаментальное правило
архитектуры Ports & Adapters, архитектуры Onion и чистой архитектуры Clean.
Заключение
Как всегда, цель состоит в том, чтобы получить разъединённую кодовую базу с высокой
связностью, в которой можно легко, быстро и безопасно производить любые изменения.
Планы бесполезны, но планирование — это всё. — Эйзенхауэр
https://habr.com/ru/post/427739/ 20/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Эта инфографика — карта концептов. Знание и понимание всех этих концептов помогает
спланировать здоровую архитектуру и работоспособное приложение.
Однако:
Карта — это не территория. — Альфред Корзыбский
Другими словами, это всего лишь рекомендации! Приложение — это территория,
реальность, конкретный вариант использования, где нужно применить наши
знания, и именно оно определяет, как будет выглядеть настоящая архитектура!
Мы должны понимать все эти закономерности, но также всегда нужно думать и
понимать, что именно нужно нашему приложению, как далеко мы можем зайти
ради разъединения и связности. Это решение зависит от множества факторов,
начиная с функциональных требований проекта, до сроков разработки приложения,
срока его службы, опыта команды разработчиков и так далее.
Вот так я всё это для себя представляю.
Эти идеи немного подробнее рассматриваются в следующей статье: «Больше, чем
просто концентрические слои».
Теги: DDD, Hexagonal, Onion, Clean, CQRS, Screaming Architecture, Clean Architecture,
архитектура приложения
Хабы: Анализ и проектирование систем, Проектирование и рефакторинг
Редакторский дайджест
Присылаем лучшие статьи раз в месяц
Электропочта
1271 0
Карма Рейтинг
Анатолий Ализар @m1rko
автор, переводчик, редактор
https://habr.com/ru/post/427739/ 21/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Комментарии 14
mkuzmin
25.10.2018 в 18:00
Буду рад, если посмотрите мою книгу на эту же тему и пототип.
+1 Ответить
claygod
25.10.2018 в 21:15
В своей книге на странице про чистую архитектуру вы написали: Use Case не могут
вызывать друг-друга. Что вы под этим подразумеваете? Интеракторы не могут общаться
между собой?
0 Ответить
mkuzmin
25.10.2018 в 21:23
Полная цитата:
Use Case не могут вызывать друг-друга.
При взгляде на код сценария мы должны видеть все его шаги.
Никто не запрещает выносить общий код в службы(service).
Речь идет о системе по модели запрос-ответ.
Интерактор — нечто, что обрабатывает некое событие.
Например, запрос пользователя или сообщение от сторонней системы.
В этом контесте интеракторы не общаются между собой напрямую.
Естественно, что пользователь может передать данные от одного интерактора другому.
Да, итеракторы не вызавают друг-друга, но могут разделять логику через сервисы.
0 Ответить
claygod
25.10.2018 в 22:31
Я считаю, что в пределах слоя все сущности могут совершенно спокойно общаться
между собой, если же нет, то по сути вы этот слой разбиваете на кучу слоёв, которые
между собой должны общаться согласно общих правил чистой архитектуры. Если же
обсуждать именно запрос-ответ, то тогда кого спросили, тот и ответил, а всё остальное
«общение» интерактора инкапсулировано (не видно).
Пишу это конечно не ради того, чтобы поспорить, а скорее поделиться своим видением.
Вот «горячий» пример из моей практики: данные через входной шлюз поступают на
слой сценариев, проходят сквозь «каналы и фильтры» и выходят через выходной шлюз.
https://habr.com/ru/post/427739/ 22/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
VolCh
26.10.2018 в 12:21
Как по мне, то общения программных сущностей (в широком смысле слова) на одном
слое стоит избегать за исключением доменного слоя. Общую логику или выносить на
слои ниже, или на слое выше создавать какой-то оркестратор или координатор.
0 Ответить
DexterHD
26.10.2018 в 13:03
В целом перевод хороший. В начале вообще супер, под конец немного путано на мой взгляд.
Автором (без претензий к переводчику) на мой взгляд упущены некоторые важные
концепции.
Например ничего не сказано про анемичную модель домена (Anemic Domain Model). Мартин
Фаулер и некоторые другие эксперты считают такую модель анти-паттерном, который на
корню убивает все преимущества ООП. (Предлагается рассматривать, бизнес-модель не как
класс с сеттерами и геттерами, а как набор классов, функций и т.д. тесно
взаимодействующих друг с другом. В данной модели важно в первую очередь поведение а не
данные).
Так же не совсем раскрыта тема ограниченных контекстов, а это очень важная тема.
Ее понимание ключ к знанию о том, где можно скопировать данные и создать новый объект/
класс/значение, а где дублирование кода — явное зло.
Если быть конкретнее, Сущность (Entity) вообще ни как не выражается внутри кода. В
зависимости от Ограниченного Контекста (Bounded Context), внутри кода Entity может быть
https://habr.com/ru/post/427739/ 23/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
представлена как простым типом типа ID, так и объектом-значенем (Value Object) или даже
целым Агрегатом (Agregate).
Например в одном контексте сущность Consumer может быть агрегатом Consumer с кучей
бизнес-логики, а в другом контексте всего лишь полем consumer_id, или value-object-ом
Consumer хранящим только несколько интересующих нас полей.
0 Ответить
VolCh
26.10.2018 в 13:18
> Если быть конкретнее, Сущность (Entity) вообще ни как не выражается внутри кода.
Если выражаться языком DDD и ООП, то Entity DDD представляется обычно объектом со
сложным поведением, возможно агрегирующим другие Entity. ValueObject или просто
значения не представляют Entity.
Другое дело, что сущность предметной области (не путать с Entity в DDD) в одном Bounded
Context может представляться Entity, а в другом ValueObject, при этом ValueObject может
иметь ссылку на Entity из другого Bounded Context, а может не иметь.
0 Ответить
DexterHD
26.10.2018 в 18:06
VolCh
31.10.2018 в 11:39
Есть предметная область, в ней есть в ней сущности с отношениями, есть процессы, есть
события — это не DDD, это по сути термины для проанализированных и
систематизированных объективных фактов, рабочие термины аналитиков, не
разработчиков.
DDD же осуществляет маппинг этих терминов на средства выбранного ЯП, очень часто
ООЯП. И вот тут объективная сущность предметной области субъективно моделируется
разного рода объектами в зависимости от контекста. И объективная сущность может
https://habr.com/ru/post/427739/ 25/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
моделироваться в ООЯП как DDD Entity а может как DDD ValueObject, а может как
скалярное значение. DDD частично позаимствовала термины из анализа, но
однозначного соответствия нет. Грубо говоря, если аналитик выделил в предметной
области какую-то сущность, это ещё не значит что в нашей системе она будет
представлена как DDD Entity в "папочке" Domain.
И я сомневаюсь, что Фаулер говорил " entity может являться Агрегатом и собритать в
себе все остальные, в другом просто ValueObject-ом", скорее он говорил "entity
(объективная сущность) может быть представлена ...". Человек (физическое лицо) —
например, это сущность, но он может быть представлен в DDD простым классом
PersonEntity, сложным PersonAggregate или примитивным PersonValueObject, а может
быть абстрагирован до какого-то Agent или User
0 Ответить
DexterHD
31.10.2018 в 15:41
https://habr.com/ru/post/427739/ 26/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Наличие DDD вообще не значит что в коде будут классы «Entity» и папочки «Domain».
Что касается аналитиков, уж извините, предвзято к ним отношусь. Все аналитики из
тех с которыми я встречался почему то никогда ничего не программировали в своей
жизни. Там где они что-то «выделяли», приходилось по итогу идти к заказчику и все за
ними переделывать.
И я сомневаюсь, что Фаулер говорил " entity может являться Агрегатом и собритать
в себе все остальные, в другом просто ValueObject-ом", скорее он говорил «entity
(объективная сущность) может быть представлена ...». Человек (физическое лицо)
— например, это сущность, но он может быть представлен в DDD простым классом
PersonEntity, сложным PersonAggregate или примитивным PersonValueObject, а
может быть абстрагирован до какого-то Agent или User
понятия бизнес-анализа, как лучше организовать код чтобы избежать множества его
изменений при изменении одного требования, не важно бизнес-логики или UI.
Вот цитата Эванса:
Мыслит он объектами и потоками изменяющихся данных, вот почему ООП —
дефолтная парадигма для применения DDD :)
Но вообще начиналось всё с
Сущность (Entity) вообще ни как не выражается внутри кода. В зависимости от
Ограниченного Контекста (Bounded Context), внутри кода Entity может быть
представлена как простым типом типа ID, так и объектом-значенем (Value Object)
или даже целым Агрегатом (Agregate).
Так вот, сущность бизнес-анализа может выражаться в DDD ООП коде полноценным
объектом, объектом-значением или простым значением. Иногда полноценный
объект ещё и корнем агрегата является или принадлежит отдельному объекту
агрегата.
В этой фразе классическое смешение контекстов :) Сущность предметной области,
если попадает в ограниченный контекст, может выражаться в коде несколькими
видами, в том числе объектом сущности. Обычно если сущность предметной области
в этом контексте изменяется или нужна история изменений, то в коде она
выражается объектом сущности. Этот объект может являться и корнем агрегата, но
это не обязательно — в контексте могут быть одновременно неизменяемые
сущности, а может быть вообще только одна.
0 Ответить
Публикации
https://habr.com/ru/post/427739/ 28/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
https://habr.com/ru/post/427739/ 29/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
ВОПРОСЫ И ОТВЕТЫ
ЧИТАЮТ СЕЙЧАС
Осторожно: осознанный
https://habr.com/ru/post/427739/ 30/31
19/01/2023, 00:36 DDD, Hexagonal, Onion, Clean, CQRS… как я собрал всё это вместе / Хабр
Настройка языка
Почему стандарты ИСО не публикуют в открытом доступе?
5.4K 31 +31
Техническая поддержка
DevOps-инженерам приготовиться: мы запустили Сезон Kubernetes
РАБОТА
Cистемный аналитик
533 вакансии
Все вакансии
https://habr.com/ru/post/427739/ 31/31