Обновить
4

Пользователь

0,1
Рейтинг
Отправить сообщение

>Это и есть legacy-форма, протекающая через границу. На уровне БД она оправдана

На уровне БД стоит check-constraint'ы навешать для контроля состояния. Но
1) о них почему-то реально мало знают
2) DDD-исты часто выступают против _любых_ проверок на уровне БД.

Я же считаю такие структурные проверки в БД оправданными. Стоят они дёшево, а от разных неожиданностей хорошо спасают.

Спасибо за развёрнутый ответ.

Ну, это вариант компенсации саги, для этого провайдер должен поддерживать методы отмены. В статье этот момент явно не оговаривается.

Спасибо, хорошая статья.
Но можно ли подробней осветить этот момент?

>Если провайдер не поддерживает ничего из этого – оборачиваем его в свой адаптер с собственным reference-store и реализуем двухфазную модель на свой страх и риск. Это работает, но добавляет новые точки отказа.

Если провайдер не поддерживает "ничего", то какими приседаниями вы от него добиваетесь идемпотентности?

Забавно то, что скорее всего - разные.
Есть известный тест на объединение двух таблиц 4 способами.

select a.* from a, b where a.x = b.y
select a.* from a inner join b on a.x = b.y
select a.* from a where a.x in (select y from b)
select a.* from a inner exists (select * from b where b.y = a.x)

Запросы логически эквивалентны (в случае уникальности b.y). Но на Oracle я получал для них 3 плана выполнения запроса, а на Postgres - 4.

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

А почему "никто вам об этом не скажет"? Книжки с мануалами совсем перестали читать?

В общем, без просмотра плана выполнения запроса - это гадание. Тем более без указания конкретной базы данных.

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

Хватает ситуаций, когда комментарии нужны, но не вида userId // идентификатор пользователя.
Но уметь писать полезные комментарии и не писать бесполезных - это отдельный сложный навык. А если в компании отсутствие комментариев приравнивается к "красному флагу", то могу представить, какой бардак там творится.

Интересный вариант.

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

Что ж, называйте DTO всё, что вашей душе угодно, не буду спорить.

Не знаю, может вам и получится найти источник, в котором говорится, что предназначение DTO - передача данных между слоями приложения, но в том источнике, на который вы сослались, говорится следующее: "The application service is then responsible for moving data back and forth between the DTOs and the domain objects". Т.е., ровно о том, про что я писал выше.

>Выражение “data transfer” означает передача данных, это и есть определение. Передавать можно и между разными слоями приложения.

В программировании всё везде передаётся. Параметры передаются в методы, результаты передаются обратно, между "слоями" приложения тоже можно параметры передавать, хотя физически в приложении никаких слоёв нет, это условность и абстракция. Всё можно назвать "data transfer", поэтому выделять некие объекты в DTO на основании того, что они куда-то "передаются" и это их "определение" просто бессмысленно. Любой параметр на этом основании можно было бы назвать DTO.

А отличие объектов DTO от любых других объектов, классов, параметров состоит как раз в их особом предназначении - передача данных между разными процессами и разными системами. Это накладывает на них специфические условия, которые в первую очередь касаются вопросов сериализации.

Ну, понятно, что с оплату заказа надо делать в одной транзакции, если всё хозяйство в рамках одной БД находится. И это совсем не сложно, если просто делать, а не выяснять какой агрегат на ком стоит.

DTO между подсистемами данные передаёт. Между фронтовым клиентом и бэком. Между двумя сервисами по шине данных. Между бэком и хранимкой (и такое может быть). Поэтому DTO сильно привязан к вопросам инфраструктуры. DTO в REST API должен быть написан таким образом, чтобы с ним было удобно работать REST-клиенту, чтобы его было удобно сериализовать и валидировать. К DTO, привязанным к другим источникам инфраструктуры, эти источники будут диктовать другие требования. Я долгое время работал с системой, внешний API которой был сделан в виде хранимок БД, которые возвращали документы XML. Соотвестственно и классы DTO должны были учитывать всю эту специфику XML, которой совсем не мало.

А маппинг из DTO в доменнные объекты - это как раз ответственность слоя приложения, чтобы DTO между слоями не гонять. Делать доменные модели осведомлёнными о DTO или не делать - это, конечно, дело вкуса, но никакого "по определению" тут нет.

>Потому что DDD ставит задачу написать бизнес-логику так, чтобы её мог прочитать и проверить не программист, а бизнес-заказчик.

Во, вот это на мой взгляд ключевое! А очень люблю книгу Эванса, особенно в её soft-части, но вот эта его идея про "код, как книга" и "чтение кода бизнесом" повела куда-то не туда. А последователи-евангелисты, вместо того, чтобы этот тезис скорректировать, только всё усугубили.

>идеальный способ реализации: загрузить все документы из базы в класс Documents, вызвать на нём метод add, который провалидирует, возможно ли это добавление, по всем необходимым бизнес-правилам, в том числе и проверит уникальность

Прекрасный "идеальный способ", который просто не гарантирует результата. Потому что ничего не знает о конкурентных запросах. Если у вас нет ограничения уникальности на уровне БД, то вы с вашей проверкой в "правильном месте" (на уровне модели) просто не застрахованы от получения дублей. Если констрейнт уникальности в БД, всё-таки, висит, то один из запросов закончится 500 ошибкой из-за не перехваченного исключения. Если вы, всё-таки, озаботились перехваткой исключения (не знаю, на каком уровне вы это сделаете в "чистом коде") и будете возвращать пользователю понятную ошибку, то зачем вам дублировать логику на уровне модели, грузить все документы и так далее? Наконец, вы можете втащить в модель свой отдельный механизм блокировки (redlockk, например) и с его помощью защищаться от конкурентности. Но зачем дублировать ту инфраструктуру, которую БД и так предоставляет в явном виде?

Записью в табличку TransactionalOutbox можно сделать. Ваш предложенный код всё равно страдает проблемой, что сущность может создаться, а письмо не будет отправлено. Если при отправке письма возникнет исключение, тогда либо пользователь получит ошибку, при том, что нужная сущность была создана и пользователь к своему удивлению обнаружит её при очередном обновлении списка. Если же перехватить исключение, то письмо не будет отправлено, а пользователь получит 200 при неполностью выполненном запросе.

Но с вашим основным посылом я согласен. Перетаскивать всю бизнес-логику на уровень моделей - это делать модели очень сложными. Модель и так должна отвечать: 1) за своё согласованное состояние, 2) за персистентность (как бы это ни отрицали сторонники "чистого" DDD). Навешивать сюда ещё взаимодействие с внешними сервисами... Ну, такое.

P.S. Сторонники "чистого" DDD вспомнят в этом случае о доменных событиях, но это просто новый слой вопросов. Если у вас в требаниях была логика "после A должно быть B и C", то в коде сервиса это будет простой линейной историей, а в коде на основе событий это превратится в винегрет.

А зачем вам в фабрике передавать ошибки валидации в виде исключений? Это и в конструкторе можно сделать. На мой взгляд, введение фабрик для создания доменных моделей как раз и имеет основной целью избавление от исключений.

Но всё равно получается громоздко. Тут проявляется одна из основных проблем DDD, о которой не много говорят, что DDD плохо скрещивается с инфраструктурой.

На инфраструктурном уровне у нас есть прекрасная библиотека валидации - FluentValidation, которая может в хорошо форматированном виде рассказать нам о проблемах входной DTO. C DDD нам либо:
1. Не использовать Fluent, а использовать фабрику, способную вернуть "доменную" ошибку валидации, а потом маппить эту ошибку на поля DTO, чтобы вернуть в ответе REST. Жутко трудозатратно.
2. Внедрить в доменную фабрику IFluentValidation, но тогда доменная фабрика будет знать о "внешнем" по отношению к домену слое DTO.
3. Так или иначе - дублировать логику. Полноценно использовать FV на уровне API, в доменке дублировать проверки и кидать простые исключения в случае ошибок (при правильном pipeline данных до этих исключений всё равно не дойдёт). Ну, или забить на такие проверки на уровне домена, полагаться на проверку на уровне Приложения.

В общем, как ни крути, либо не совсем фэн-шуй, получается, либо много дополнительной работы для приближения к фэн-шую.

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

Вообще, на мой взгляд главная проблема DDD в фетише отделения доменного кода от инфраструктуры. Для валидации DTO есть такая удобная вещь, как FluentValidation, для работы с БД такая удобная штука, как EF. Но FV с проверкой доменной модели скрещивается плохо, потому что либо доменная модель должна знать о DTO, чтобы использовать Fluent, либо проверки на уровне доменки и API должны быть продублированы. Либо полагаться на входную валидацию и считать, что к нам приходят "правильные" DTO (одной лишь структурной валидации для проверки валидности ведь мало).

С персистентным слоем + ORM та же проблема. Ты персистентность из моделей в дверь, а она влетает в окно. Времена, когда EF диктовал, что "доменная модель" - это набор get/set-свойств, давно прошли, современный EF куче всего научился. Но уши персистентности всё равно то там, то тут торчат. Не десериализуешь ты сложную модель нормально без доступа к приватным полям через рефлексию или без специального конструктора.

Так то мне нравится ключевая идея DDD про отделение кода доменной логики от кода инфраструктуры/представления, но мало кто готов честно говорить о возникающих на этом пути проблемах. У всех DDD-евангелистов получается ответ в виде "вы просто не умеете его готовить".

1
23 ...

Информация

В рейтинге
3 579-й
Зарегистрирован
Активность