Search
Write a publication
Pull to refresh
8
46.1
Send message

Ну, т.е., иными словами.

Даже Вернон, при всей его любви к eventual consistency, вовсе не запрещает сохранять несколько агрегатов в одной транзакции, напротив, рекомендует это делать в некоторых случаях.

Microsoft, в свою очередь, в той самой статье, которую вы привели, упоминает о том, что разные проектировщики делают это по разному, но сами они предпочитают именно мой вариант (воплощённый в их основном архитектурном примере, к слову).

Так делаю я, так делают многие другие.

Но, разумеется, признать свою неправоту - выше ваших сил. И вы продолжаете настаивать на том, что "However, there is nothing preventing you from committing modifications to two or more Aggregates in a single atomic database transaction. " означает прямой запрет это делать в дурацком ddd.

Ну и смысл с вами разговаривать, коллега? Ладно бы вы могли предложить работающую альтернативу, так ведь нет, вы настаиваете на том, что ddd - нерабочая хрень. Ну да, ddd в трактовке таких, как вы, действительно нерабочая хрень. Именно поэтому я и потратил время на написание этой публикации.

более того выше вы отстаивали, что данное решение нерабочее.

Ну, это уже совсем ни в какие ворота... как бы я мог отстаивать такую глупость, если сам так постоянно делаю? В статье чётко проведена грань между доменными событиями и интеграционными (последние могут быть in-process, тут нет никакой проблемы).

Я вам уже объяснял, что дешёвым это изменение может не быть. Потребуется изменить и UI/UX

вы объяснили неправильно

Да, если кто-то облажался настолько эпично, как вы описали, то да, конечно придётся.

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

При этом проблему конкурентных обновлений вы таким образом всё равно не решаете (хотя и ослабляете), потому что они прекрасно могут случиться и в одном агрегате тоже.

Поэтому, разумеется, надо проявлять гибкость и опираться на здравый смысл. Именно это и позволяет сделать связка из доменных событий, обрабатываемых в единой транзакции, и интеграционных событий, обрабатываемых в отдельной транзакции.

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

одна из - да, но не основная, конечно

Агрегаты защищают свои инварианты, инварианты других агрегатов не должны влиять на успешность транзакции.

это ваше, странное, неправильное мнение, я не знаю, откуда вы его взяли
это решать application слою, в зависимости от ситуации, агрегата это не никак касается, это не его дело и вообще не дело доменного слоя

дело доменного слоя в этом вопросе - доменное событие сгенерировать, но на этом - всё

Сам по себе без оркестрации апликейшен слоем собрать какой-то мало-мальски полезные процесс не получится.

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

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

эммм... нет
ну то есть такое тоже бывает, но, в общем случае, разумеется, нет

как вы верно сказали в начале - одной из целей разработки агрегата является желание избежать конфликтов обновления
это, вроде, очевидно

конечно, ddd и агрегаты тут совершенно непричём, эта проблема в любом случае возникнет, какой методологией разработки вы бы не пользовались
вода - водяная, масло - масленное

если вероятность конкурентных обновлений низка - обновляйтесь в одной транзакции и будет вам и простота и эффективность и счастье
если вероятность конкурентных обновлений существенна - разрывайте транзакцию

ошиблись? исправляйте ошибку в application layer, доменный слой при этом останется незатронут

However, there is nothing preventing you from committing modifications to
two or more Aggregates in a single atomic database transaction. You might choose to
use this approach in cases that you know will succeed but use eventual consistency for
all others.

Domain-Driven Design Distilled
Vaughn Vernon

да, но моё странное понимание - работает на практике, в реальных проектах

ваше странное понимание, по вашему же признанию, не работает и работать не должно

и теперь, внимание, следите за руками, вы настаиваете на том, что правильное понимание - это ваше понимание, то самое, в котором ddd - это неработающая, неприменимая на практике чушь

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

э-э-э-э-э... ну, спасибо, нет

Конкретно этот минус поставил я. Разумеется, не за цитату, а за попытку выдать своё, кхм, странное понимание этой цитаты за аргумент в споре.

"Transactions should not cross aggregate boundaries." - транзакции не должны ПЕРЕСЕКАТЬ границы агрегатов, т.е. не должны их нарушать, не должны разрушать защиту инвариантов, которую создают агрегаты. Агрегат должен меняться по принципу всё-или-ничего.

Фаулер не говорил, что транзакции не могут ОБЪЕДИНЯТЬ сохранения разных агрегатов, это было бы полной глупостью, не правда ли? Какой в этом, даже чисто теоретически, был бы смысл?

Пока архивирали аккаунт, кто-то позвонил, счетчик числа пропущенных звонков на аккаунте обновился, транзакция упала, т.к. не прошла оптимистичная конкурентность по аккаунту.

Извините, конечно, но вы упорно путаете тёплое с мягким.

Ошибки конкурентного обновления точно также могут встречаться и внутри одного агрегата - да сплошь и рядом. Это тут вообще непричём, абстракции совершенно разного уровня.

Агрегаты защищают инварианты, а не спасают от конкурентных обновлений :)

Жизнь заставит вас пересмотреть свои взгляды, когда наиграетесь в DDD на локалхосте и начнёте масштабироваться.

Ну да, я же, убогий, за 20+ лет практики реальных-то приложений не видел...

В этом месте корабль DDD затонул из-за дырявой абстракции

А причём тут вообще DDD? А он тут совершенно и непричём. Вообще никакого отношения к вопросу не имеет.

Хотите сохранять данные в одной транзакции, чтобы не возиться с eventual consistency? DDD вам это позволяет сделать.
Сталкиваетесь с редкими ошибками конкурентного обновления? DDD вам не мешает использовать retry.
Не можете себе позволить сохранять несколько сущностей в одной транзакции (из-за конкурентных обновлений или хранения данных в разных storage)? DDD вас полностью поддерживает.

Вообще никакой проблемы нет.

И что значит "репозитории в принципе не нужны"? Если я, допустим, не следую DDD, то мне репозитории не пригодятся?

если так, тогда вам не стоит лезть со своим пониманием того, что такое репозиторий, в разговоры про DDD
считайте это созвучным словом на другом языке

Их много. Их сложно назвать и их незачем выносить в отдельный именованый тип.
в доменной логике или нет?

если вы не выделяете доменную логику, то вы в этом разговоре напрасно тратите и своё и моё время

просто для примера - в рамках стратегического паттерна DDD про Единый Язык прямо утверждается, что в разных поддоменах один и тот же термин может и будет иметь разные значения

я не знаю, сумел ли Эванс зарегистрировать торговую марку на термин Repository, на самом деле, но лично мне хотелось бы, конечно, запретить его использовать тем, кто DDD не практикует :)
но это так, лирика

Например что такое QueryHandlers в Application слое и чем они отличаются от DbQueryHandlers в инфраструктуре?

QueryHandlers ~= CommandHandler
DbQueryHandlers - метод исполнения эффективных запросов на чтение
QueryHandlers скорее всего будут использовать DbQueryHandlers, но это не точно

А что за Application Services и чем отличаются от domain сервисов?

классы-сервисы могут быть и в application-слое, почему нет?
логики в этом слое вообще немало, просто это не та логика, которую вы бы захотели обсуждать с дедком 82 лет от роду, который называет компьютеры ЭВМ

Что скажите насчет UseCase и UserStory?

Скажу, что это прекрасные термины, не имеющие особого отношения к тактическим паттернам DDD

А чем это отличается от спецификации?

Тем, что вам не приходится изобретать свой язык, конструировать его выражения в одном месте и деконструировать его в другом, я полагаю :)

И при этом у вас не возникает дублирование, но метод IsVip по прежнему - часть вашего доменного слоя.

Как вы создаете сущность из DTO, которое пришло из контроллера?

в application layer, в обработчике события, вызываю фабричный метод и сохраняю транзакцию

опять же, см. раздел про Read Model, некоторые методы применения Value Object, на мой взгляд, вполне допустимы

И да, я бы на первое место всё-таки ставил агрегат, а репозиторий — это его скромный слуга.

это чисто методологический вопрос - как лучше объяснять

мне кажется, что объяснить смысл агрегата без понимания (или, ещё хуже, с неправильном пониманием) репозитория - намного сложнее, но это просто моё мнение

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

Этот код описывает доменную логику определения того, является ли клиент VIP или нет.
Но при этом он оформлен в таком техническом виде, что портит открытый интерфейс entity, что плохо.

Зачем? Чтобы избежать необходимости выноса логики домена в реализацию репозитория или её дублирования в отдельном классе, что, собственно, не просто не лучше, а ещё хуже. Компромисс.

Если же это условие не нужно доменной логике, то и делать ему в ней, разумеется, тем более нечего.
Об чём я и писал довольно подробно.

Кто является главным потребителем Repository? Application Layer.

Да. Равно как основным потребителем Агрегатов. Это ровно то же самое - удобный интерфейс для работы с доменным слоем, не больше, не меньше

Опять же, Aggregate это граница транзакционности. Если вы получаете несколько агрегатов через List
или аналоги, а потом обновляете их все вместе - вы нарушаете данные границы

Нет, никакие границы тут, разумеется, не нарушаются, да и с чего бы вдруг? Я понимаю, что это ваша трактовка, но в ней же нет никакого практического смысла (с такой ошибкой в восприятии DDD я, к слову, ещё не сталкивался).

Транзакционные границы Агрегата не имеют прямого отношения к транзакциям Хранилища, это вообще про другое - инварианты Агрегата не могут нарушаться из-за частичного обновления, но сохранять или не сохранять несколько Агрегатов вместе - совершенно нормально и ничему, конечно же, не противоречит. А если бы вдруг был такой принцип - так отправляйте его на свалку, он же явно вредительский.

Самый неочевидный момент, это то, что все обработчики доменных событий выпоkняются синхронно

Я не знаю, что конкретно вы подразумеваете под синхронным выполнением, видимо, сохранение данных в рамках одной транзакции. Да, разумеется, и это главная фича доменных событий.

Может ли это привести к ошибкам при параллельном сохранении разных объектов? В зависимости от вашей схемы это может быть как вполне вероятно, так и крайне маловероятно.

Если это в вашем конкретном случае вероятно, значит, вам следует разорвать транзакционный контекст на несколько с помощью интеграционных событий и паттерна outbox. Если это (а как правило так и есть) маловероятно - просто обеспечьте логику retry на исполнение команд при обнаружении исключений, связанных с этим, это легко реализуется и встраивается в command execution pipeline.

Для начала, DDD не знает такого понятия, как "бизнес-логика". Этот слой совершенно сознательно разбивается на два разных - логика приложения и логика домена.

Если вы не согласны с этим исходным утверждением и полезностью этого разбиения, то дальнейшая дискуссия попросту не имеет смысла - DDD не для вас и репозитории вам в принципе не нужны, DbContext - разумный выбор.

Далее, вот этот вот набор фильтров имеет какой-то смысл с точки зрения доменной логики?
Не старше 5 лет назад, на координации и от уволенного менеджера (что, к слову, невозможно сделать в рамках разумного агрегата, сразу вам скажу), вот это вот условие имеет какой-то смысл?

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

Если же эта логика (как это обычно и бывает в таких случаях) имеет смысл именно для Application слоя, то вас никто не заставляет в принципе каким-то образом приплетать сюда репозиторий, это изначально ненужное самоограничение.

////

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

Защищать агрегаты, конечно, а для этого вам придётся конструировать эффективные запросы к вашему хранилищу, не выставляя наружу потроха (в вашем случае AccountManager), а это по факту можно сделать только внутри репозитория, но никак не снаружи.

Не выходящие за пределы агрегатов? Я, опять же, прямо в публикации показал - как именно это можно делать. Но я никому ничего не навязываю, я просто показываю, как это можно сделать эффективно.

спасибо

ну а какие критерии могут быть, вы всё правильно говорите

если это связка A->B, то да, прямо в обработчике команды
если это что-то более сложное, то зависит от того, куда проектировщик склоняется в дихотомии оркестрация <-> хореография, т.е. либо доменный сервис, либо доменные события

оркестрация через сервис выглядит проще, но только до определённого уровня сложности/линейности этой логики

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

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

Браво. Ответ который мы заслужили

Чтобы что-то заслужить, нужно предпринять какие-то конкретные действия.

В данном случае вы просто усиленно делаете вид, что вам кто-то что-то навязывает.
Это не просто не так - это бесконечно далеко от реальности. Как это обычно и бывает - вы просто часть аудитории, а я просто автор, который решил поделиться с этой аудиторией своим опытом и своими соображениями. Не больше, не меньше.

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

Value Object позволяет разгружать Entity за счёт методов, работающих исключительно с одним свойством или логически связанным набором свойств.

Value Object позволяет создавать wrapper-ы над простыми типами данных, которые помогают не нарушать инварианты.

Address оттягивает внимания от этих важных и часто полезных концепций создавая ложное впечатление, что Value Object это что-то про повторное использование или, на крайний случай, явное выделение доменных концепций.

И то и другое хорошо и важно, но акцентировать внимание всё равно следует на первых двух пунктах, как не менее полезных, но менее очевидных, чему Address ни разу не способствует.

Дело в том, что с вынужденной сложностью (сложностью домена) бороться бессмысленно, она просто есть, от неё нельзя избавиться, её можно только перераспределить - так или иначе.

Соответственно DDD это история про то, как сложность предметной области перераспределять так, чтобы с ней было проще работать.

Что до конкретных примеров - устройтесь на работу в FAANG и увидите, как это делается на практике. Они там, правда, как правило называют это по другому, но смысла это не меняет. В конце концов сам Эванс признавал, что он не придумал DDD, а оформил в единую систему те приёмы, которые, по его мнению, использовали лучшие архитекторы, с которыми ему доводилось работать над сложными проектами.

Проще говоря - DDD в полную силу оправдывает себя в тех проектах, которые обычно в Open Source не попадают. Большие проекты, над которыми несколько высокопрофессиональных команд согласованно работают на протяжении нескольких лет.

То, что вы хвалите, это, похоже, и есть модульный монолит. Ну или микросервисы, построенные на концепции bounded context, я не вполне уверен, что вы имеете ввиду.

А плохо организованную кашу из микросервисов, которую вы ругаете, как раз и называют "распределённым монолитом".

Спасибо за высокую оценку.

Адрес плох в качестве примера Value Object, так как его выделение в отдельную сущность слишком очевидно по не относящимся к делу причинам (много свойств + повторное использование). Поэтому он ничему толком не учит.

Хорошие примеры это Distance и BirthDay. Первый демонстрирует силу предотвращения ошибок за счёт грамотной структуры, второй - возможность разгрузить Entity.

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

1

Information

Rating
455-th
Registered
Activity