Pull to refresh

Comments 16

Любая нетривиальная система состоит из нескольких слоев (в порядке кто кого вызывает): application layer (controller) -> business services -> core domain -> infrastructure\libraries. Слои могут быть пропущены и app layer может напрямую звать core или инфраструктуру. А еще есть DI, чтобы композиция была не жестко задана в коде, а управлялась из вне самих слоев.

Но как только не извращаются разные авторы, пытаясь эту линейную конструкцию завернуть в разные геометрические формы. Сначала придумали onion architecture, там все тоже самое, но никто не понял что это. Теперь hexagonal, который от onion только формой отличается. А еще постоянно существует DDD в которой люди пытаются core domain вытащить на самый верх и построить все на нем, используя определенный набор паттернов.

Но все это не несет никакой добавленной ценности к процессу разработки. Как были контроллеры, сервисы, классы предметной области и библиотеки, так и остаются, и цепочка вызова от «применения» hexagonal\onion\domain-driven не меняется.
Это вам понятно, а для большинства неокрепших умов не понятно даже зачем нужно отделять инфраструктуру от приложения. По сути все эти луковые/гексагональные архитектуры несут в себе одни и те же идеи, вопрос в предпосылках.

Луковая — концентрирует внимание на главенстве core domain/domain layer, восхваляет persistence ignorance и т.д.

Гексагональная — мы берем луковую архитектуру и привносим концепцию портов и адаптеров как более четкое определение границы между слоями. То есть тут мы концентрируем больше внимания на границах слоев а не на том что внутри оных.

То есть оно как бы все о том же, но добавляет чуть больше конкретики на разных уровнях.

Я вот вижу ровно обратную картину.

Под воздействием подобных статей неокрепшие умы начинают отделять когда еще нечего отделять. Получается очень over-engineered код, в котором за тоннами «паттернов» не видно какая же логика работает. И такая болезнь иногда поражает даже опытных программистов.

Если посмотреть на эту статью, то в ней такой же пример оверинжиниринга, как обычно я вижу в приложениях. Куча кода, чтобы вызвать sendmail. Понятно что в реальном коде будет сложнее чем просто sendmail, понятно что в большом масштабе это даст выигрыш. Но если начинать любое приложение с такого кода, то его невозможно будет поддерживать.

Поэтому я категорически против навязывания архитектуры в таких статях.
Поэтому я категорически против навязывания архитектуры в таких статях.

Так никто ж и не навязывает.

Если вы не только просмотрите код, но и почитайте сопровождающий текст, там ясно написано, что это то к чему нужно стремиться, но не нужно слепо следовать этим правилам. Скажем в примере с добавлением логирования ясно написано что это может быть и ок, особенно если реализация нотификатора у нас одна, но это увеличивает технический долг. Просто добавляются еще и примеры, почему это увеличивает технический долг, при каких ситуациях нам это скажется и как вообще быть.

В целом я решил перевести эту статью как раз таки из-за весьма прагматичных подходов применимых в реальности, а не потому что там расписана клевая архитектура.

Но в целом я с вами согласен, что люди могут неверно истолковать идеи и броситься из одной крайности в другую.
когда возникает желание что-то выделить в отдельный слой, абстракцию и тд вспоминаю цитату
Управление сложностью — самый важный технический аспект разработки ПО. По-моему, управление сложностью настолько важно, что оно должно быть Главным Техническим Императивом Разработки ПО


если желание осталось — значит действительно надо выделять :)
а вообще с каждым годом МакКоннел открывается в новом свете
UFO landed and left these words here
придумали ее джависты давным давно. И придумали ее когда много работали с микросервисами и прочими непотребствами.
UFO landed and left these words here
альтернативное название — «ports and adapters». В статье приведена ссылка на первоисточник.
Допустим есть сущность User с полем email.
Есть команда RegisterUserCommand и событие UserCreatedEvent.
Вопросы:
— Допустим RegisterUserCommand должен создавать пользователя и отправлять ему на почту письмо. Нормально ли реализовывать это требование прямо в обработчике команды, или нужно создавать ещё событие UserRegisteredEvent?
— Не глупо ли создавать события и их обработчики в одном слое, а связывать их в другом?
— Где валидировать сущности которые зависят от данных? Например нужно проверять E-Mail на уникальность.
— Мне кажется или бизнес правила разносятся по всему коду?
— Как потом с такой кашей работать?
— Не слова о транзакциях. Не должна ли команда оборачиваться в транзакцию?
— Допустим RegisterUserCommand должен создавать пользователя и отправлять ему на почту письмо. Нормально ли реализовывать это требование прямо в обработчике команды, или нужно создавать ещё событие UserRegisteredEvent?


Я обычно такие вещи разруливаю на уровне ивент листенеров по postFlush (когда пользователь на самом деле создался). То есть по postFlush я могу достать все сущности из unit of work учавствовавшие в транзакции. Потом из всех сущностей собираются все доменные ивенты и отправляются на выполнение в ивент диспатчер. Но это при условии если у вас есть такая возможность.

У автора статьи ситуация чуть отличается в силу использования active record. Тут уже есть другие варианты, но в принципе опять же, ваш сервис отправки нотификашек может не сразу отправлять нотификашку, а запоминать что ее надо отправить, и реализация будет ожидать окончания транзакции (какой-то системный ивент).

Опять же идея юз кейсов идет из луковой архитектуры и там суть в том что на каждый юз кейс приложения есть метод, один и только один. А как вы это дело будете реализовывать — все зависит от задачи которую вы решаете.

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

обработчики событий обычно будут находиться в application layer, и может быть иногда во framework layer для каких-то задач связанных с инфраструктурой (например синхронизация write model и read model если мы практикуем CQRS и при этом у нас данные в разных хранилищах но это детали реализации, инфраструктура).

Связываются они опять же диспетчером событий. Так как интерфейс оного определен в application layer то значит и принадлежит он application layer, а уж где его реализуют и как он работает — дело третье.

Где валидировать сущности которые зависят от данных? Например нужно проверять E-Mail на уникальность.

Сущности не должны иметь в принципе возможности войти в невалидное состояние. Валидировать вы должны входящие данные (например, как рассматривалось в статье, декоратор над CommandBus реализующий валидацию).

Альтернатива — попробовать достать из репозитория чувака с таким email-ом и если он найден, уже как-то реагировать (от бросания исключения до предложения восстановить пароль + нотификация владельцу аккаунта). Это будет более правильный способ так как таким образом в нашем хэндлере будет явно описано это бизнес правило.

Мне кажется или бизнес правила разносятся по всему коду?

Все бизнес правила инкапсулируются в виде сервисов или же в методах сущностей (общие бизнес правила для конкретной сущности).

Как потом с такой кашей работать?

Никакой каши, все красиво и удобно. Особенно если писать это все с применением TDD (вся бизнес логика при таком подходе вообще не зависит от инфраструктуры или фреймворка, что означает что мы можем спокойно покрыть все быстрыми юнит тестами).

Не слова о транзакциях. Не должна ли команда оборачиваться в транзакцию?

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

Так же мы можем разделить различные команды по типам (Command, Query или write-command и read-command). Словом… это относится уже к реализации. Как я уже говорил — гексагональная архитектура делает больше упор на взаимодействие между слоями, описание зависимостей. Она потому еще и называется архитектурой «портов и адаптеров».
Если вам интересно узнать подробнее как организуются бизнес правила, рекомендую посмотреть вот этот доклад от Mathias Verraes: Unbreakable Domain Models. Опять же в условиях простой бизнес логики все это может быть оверхэдом, потому не стоит впадать в крайности.
Выглядит интересно. Может быть есть сопровождающий пост к этому репозиторию?
Неа, только код. На реддите вчера было обсуждение, но там большинство восприняло это все в штыки так как код вопервых не идеален (главное там показать структуру всетаки), и выглядит все слишком сложно для такой задачи.

В целом там идеи те же что описываются в этой статье, но просто идут более классические для многослойной архитектуры термины (вроде инфраструктура вместо framework layer), а так тоже самое, та же инверсия зависимостей для снижения связанности слоев.
вчера проревьюировал приложение, а сейчас прочел ветку реддита — согласен с вашим комментарием. Это самый подробный и более-менее реальный пример многослойного приложения.
Соглашусь и с комментариями противников такого подхода в контексте констатации факта — код приложения очень многословен. Но нужно понимать для чего это делается и что мы благодаря этому получаем.
Only those users with full accounts are able to leave comments. Log in, please.