Re[7]: DDD для небольших проектов.
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 08:14
Оценка:
Здравствуйте, Poopy Joe, Вы писали:


PJ>Есть куча статей и видео на эту тему. Даже тут что-то приводилось. Если ты все это отказываешься смотреть, то что ты от меня ждешь?

Я жду убедительного примера применения DDD к маленькой задаче.
Примеры приводились — я их прокомментировал. Пока что оказалось, что практический пример DDD не являются кошерными, там автор применил недостаточно DDD. А остальные — не примеры, а точно такие же рассуждения про loose coupling и tight cohesion. Причём те примеры не были настолько радикальны, как вы, чтобы на каждое состояние в стейт-машине вводить отдельный тип.

PJ>Че? Да, валидность, разумеется, зависит от твоих задач. Сферической валидности в вакууме не существует. Но причем тут бинарные признаки? Заказ валиден когда ты можешь выполнить.

Что именно "выполнить"?
PJ>9 там признаков или 109 совершенно несущественно. Два типа, ну три максимум.
PJ>NewOrder -> <ValidOrder, IncompleteOrder, Error>. IncompleteOrder -> <ValidOrder, IncompleteOrder, Error> Или тебе просто хочется доводить все до абсурда?
Нет, я хочу понять, как всё работает в вашем мире.
У меня просто перед глазами прямо сейчас запущена система обработки заказов.

S>>Тем не менее, у нас по-прежнему количество типов растёт экспоненциально. И операций типа "отгрузить" у нас получается не одна, потому что отгрузка ReadyOrder переводит его в ShippedOrder, а отгрузка PaidReadyOrder переводит его в PaidShippedOrder.

PJ>Вот данном случае все зависит от бизнеса опять же. В обычном случае PaidShippedOrder нафиг не нужен, потому что Shipped может получиться только из Paid. Но если возможно оплата после отправки, то да будет PaidShipped и UnpaidShipped. Но никакой экспоненты тут не будет.
Вы просто увидели самое начало экспоненты, и она вам кажется недостаточно быстрой

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

Ну нет, не только. Разница ещё и в том, что "обычный код" позволяет настраивать бизнес-правила на готовой системе, а ваша реализация предполагает перекомпиляцию и передеплоймент.
Вот вы завелосипедили систему типов в предположении, что возможна только предоплата. А завтра к вам пришёл заказчик и говорит "а давайте введём продажу наложенным платежом". Упс, у нас помимо PaidOrder и ShippedOrder появился PaidShippedOrder, или ещё как-то. Кстати, какие при этом у вас будут отношения между этими классами? Они все потомки AbstractOrder, наследники друг друга, или просто никак не связаны?
Как выглядит сигнатура метода ShipOrder()? Не на воображаемом языке, а на настоящем языке программирования?
Как мы избегаем дублирования кода? Ведь, к примеру, PaidOrder и PaidShippedOrder оба должны требовать наличия ссылки на платёж, а просто ShippedOrder — не должен. Как мы это выражаем в коде?

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

Я понятия не имею, что вы делаете каждый день. Но Эрика Липперта почитать полезно всем, кто хоть что-то делает в IT.
PJ>Я так понимаю, на самом деле у тебя есть точка зрения, которую ты не собираешься менять и обсуждать что-то бессмысленно?
Как раз нет. Просто к моей точке зрения есть аргументы. Я вот незнаком с языками, система типов которых позволяет выразить все эти вещи натуральным образом.

PJ>>>В модели те, которые сохраняют инвариант модели. В BL которые обеспечивают логику модели, т.е. ValidateOrder : (NewOrder -> <ValidatedOrded, Error>), в AL use cases: AddArticle : ((Atricle, NewOrder) -> NewOrder), Checkout, etc

S>>Давайте конкретно. Задача есть? Есть. Перейдём от абстрактных рассуждений про валидность ордеров, и попробуем нарисовать иерархию классов для нашего простого случая с шестью сущностями, двумя сценариями, и четырьмя бизнес-правилами.
PJ>Я изложил принципы. Попробуй описать свою систему используя их и уже будем обсуждать конкретику. Иначе, если ты все сходу отвергаешь, как невозможное, то конструктивно обсуждать нечего.
Я не понимаю, как описать систему, используя эти ваши принципы. Как описать систему, используя понятные мне принципы, я и так знаю — могу продемонстрировать.
Серия статей Липперта подробно показывает, каким образом фейлится попытка описать бизнес-правила языком ООП. Либо у языка, которым вы пользуетесь, система типов мощнее чем у С# и Java, либо вы чего-то недоговариваете.

Тем более, у меня затруднения — одни фанаты DDD утверждают, что правильный способ — это тащить всю логику в агрегаты, т.к. именно так достигается loose coupling и tight cohesion. Непонятно, правда, как это ложится на изоляцию от базы.
То есть репозиторий (или кто у нас там отвечает за инициализацию агрегата) каким-то волшебным образом должен понять, что заказу для выполнения его работы надо подтащить список применимых к нему правил ценообразования, сделать это, а уж там-то заказ развернётся во всю мощь своей бизнес логики. По мне так это попахивает размазыванием логики по классам "заказ", "правило ценообразования", и "репозиторий заказов". Но может быть я неправильно понял эту идею?

Вот вы пишете, что все трансформации лежат за пределами заказа, а заказ (точнее, все эти многочисленные заказы) отвечает, вроде бы, только за валидацию "внутренних" инвариантов. Ну, так это ж анемика — вид сбоку; просто попытка выразить пре- и пост-условия в виде системы типов, а не рантайм проверок. В общем, непонятно — как правильно-то?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[5]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 14.02.20 08:20
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>на самом деле есть возможности. посмотрите например (там на F# правда, на C# так изящно неполучится, точнее можно — но это уже будет не так удобно, и даже настолько неудобно что плюсы начнут теряться).


Это не так. Ну, ну "так изящно неполучится", тут спора нет. Придется написать какое-то количество бойлерплейт кода, и сам код будет выглядеть более многословно. Ну так c# и есть более многословный. Однако все плюсы вполне себе сохранятся. Тут ведь выбор либо писать больше кода, либо дольше отлаживать.
Re[7]: DDD для небольших проектов.
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 08:31
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

Проскроллил видео про BLOBA на F#.
Вы на нём пишете?
Тогда предыдущий мой пост нерелевантен. В таком повороте ваша трактовка DDD имеет великий смысл.
Я бы сказал, что это — анемика, доведённая до совершенства Я примерно так и думал — тут недавно в этих же форумах перетирали про гипотетическую применимость ФП к бизнес-логике. Я не предполагал, что кто-то реально это использует в продакшне!

Только вот весь основной DDD, который про rich object model — ровно противоположен этому подходу. Посмотрим, смогут ли его фанаты рассказать нам что-то интересное.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[32]: DDD для небольших проектов.
От: takTak  
Дата: 14.02.20 09:00
Оценка:
S>Это предполагает наличие изменяемой сущности "заказ". Как мы уже неоднократно обсудили в этом топике, в рамках нашей задачи такой штуки нет.

есть же, наверное, какая-то история заказов или сделок? кроме того, цену на "заказ" или "корзину покупок" вроде как менеджер тоже изменить может, т.е какая-то сущность этому соответствует?

S>Пользователь сразу видит список товаров с их ценами. Скидки/надбавки ему не видны — это закрытые подробности. Всё, что он видит — конкретную сумму.


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

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

первый путь , в принципе, легко реализуем, когда есть инфраструктура для обработки сообщений и всё решение сделано в ддд-стиле,
но я подумаю на досуге о второй возможности
Re[8]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 14.02.20 09:16
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Я жду убедительного примера применения DDD к маленькой задаче.

Критерий убедительности в студию.

S>Примеры приводились — я их прокомментировал. Пока что оказалось, что практический пример DDD не являются кошерными, там автор применил недостаточно DDD. А остальные — не примеры, а точно такие же рассуждения про loose coupling и tight cohesion. Причём те примеры не были настолько радикальны, как вы, чтобы на каждое состояние в стейт-машине вводить отдельный тип.

Не понял контекст. Я ничего не говорил про стейт-машину, от слова совсем.

PJ>>Че? Да, валидность, разумеется, зависит от твоих задач. Сферической валидности в вакууме не существует. Но причем тут бинарные признаки? Заказ валиден когда ты можешь выполнить.

S>Что именно "выполнить"?
Ну я бы предположил отправить заказчику. Но это ж твоя задача, тебе должны быть виднее какие еще есть состояния у заказа.

PJ>>9 там признаков или 109 совершенно несущественно. Два типа, ну три максимум.

PJ>>NewOrder -> <ValidOrder, IncompleteOrder, Error>. IncompleteOrder -> <ValidOrder, IncompleteOrder, Error> Или тебе просто хочется доводить все до абсурда?
S>Нет, я хочу понять, как всё работает в вашем мире.
В моем мире нет заказов, поэтому меня не надо бомбардировать терминами "наложенным платежом", я без понятия в чем там отличие, но в остальном вот так и работает. Я бы хотел понять, что тут тебе кажется невозможным.

S>Вы просто увидели самое начало экспоненты, и она вам кажется недостаточно быстрой

Ну покажи ее продолжение.

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

S>Ну нет, не только. Разница ещё и в том, что "обычный код" позволяет настраивать бизнес-правила на готовой системе, а ваша реализация предполагает перекомпиляцию и передеплоймент.
Разумеется позволяет, что заставляет тебя думать обратное?

S>Вот вы завелосипедили систему типов в предположении, что возможна только предоплата. А завтра к вам пришёл заказчик и говорит "а давайте введём продажу наложенным платежом". Упс, у нас помимо PaidOrder и ShippedOrder появился PaidShippedOrder, или ещё как-то. Кстати, какие при этом у вас будут отношения между этими классами? Они все потомки AbstractOrder, наследники друг друга, или просто никак не связаны?

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

S>Как выглядит сигнатура метода ShipOrder()? Не на воображаемом языке, а на настоящем языке программирования?

Что такое настоящий язык? Я их только по именам знаю.

S>Как мы избегаем дублирования кода? Ведь, к примеру, PaidOrder и PaidShippedOrder оба должны требовать наличия ссылки на платёж, а просто ShippedOrder — не должен. Как мы это выражаем в коде?

Где тут дублирование кода?

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

S>Я понятия не имею, что вы делаете каждый день. Но Эрика Липперта почитать полезно всем, кто хоть что-то делает в IT.
Это не делат каждое его слово истиной. Так-то много кого почитать полезно. Вот тебе посоветовали почитать Влашина, но я не вижу у тебя энтузиазма.

S>Как раз нет. Просто к моей точке зрения есть аргументы. Я вот незнаком с языками, система типов которых позволяет выразить все эти вещи натуральным образом.

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

PJ>>>>В модели те, которые сохраняют инвариант модели. В BL которые обеспечивают логику модели, т.е. ValidateOrder : (NewOrder -> <ValidatedOrded, Error>), в AL use cases: AddArticle : ((Atricle, NewOrder) -> NewOrder), Checkout, etc

S>>>Давайте конкретно. Задача есть? Есть. Перейдём от абстрактных рассуждений про валидность ордеров, и попробуем нарисовать иерархию классов для нашего простого случая с шестью сущностями, двумя сценариями, и четырьмя бизнес-правилами.
PJ>>Я изложил принципы. Попробуй описать свою систему используя их и уже будем обсуждать конкретику. Иначе, если ты все сходу отвергаешь, как невозможное, то конструктивно обсуждать нечего.
S>Я не понимаю, как описать систему, используя эти ваши принципы. Как описать систему, используя понятные мне принципы, я и так знаю — могу продемонстрировать.
Так хотя бы попробуй. Попытки уже и можно обсуждать. Если у тебя все сводится к "это невозможно потому, что Липперт так сказал", ну используй что можешь. Чего тут обсуждать-то?

S>Серия статей Липперта подробно показывает, каким образом фейлится попытка описать бизнес-правила языком ООП. Либо у языка, которым вы пользуетесь, система типов мощнее чем у С# и Java, либо вы чего-то недоговариваете.

Я использую C++, F# и C#, если речь о больших проектах. А вот ооп я почти не использую (в дотнете это иногда приходится из-за фреймворка и библиотек), может поэтому ничего и не фейлится.

S>Тем более, у меня затруднения — одни фанаты DDD утверждают, что правильный способ — это тащить всю логику в агрегаты, т.к. именно так достигается loose coupling и tight cohesion. Непонятно, правда, как это ложится на изоляцию от базы.

За фанатов не скажу, но по мне все это детали реализации. Изоляция от базы она совершенно натуральна. Модель зависит от себя самой (ну еще может быть shared kernels других BC), BL зависит от модели, AL зависит от BL и модели, Инфраструктура от всего остального, включая базую. Таким образом внедрить зависимость на базу в логику просто физически не получится. Красота.

S>Вот вы пишете, что все трансформации лежат за пределами заказа, а заказ (точнее, все эти многочисленные заказы) отвечает, вроде бы, только за валидацию "внутренних" инвариантов. Ну, так это ж анемика — вид сбоку; просто попытка выразить пре- и пост-условия в виде системы типов, а не рантайм проверок. В общем, непонятно — как правильно-то?

Ну, можно конечно и так интепретировать. Я не думаю, что тут есть понятие "правильно". Важно что ты хочешь добиться. Вот лично я хочу добиться минимального времени отладки. И оно работатет, и не зависит от размера проекта.
Re[33]: DDD для небольших проектов.
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 09:18
Оценка:
Здравствуйте, takTak, Вы писали:
T>есть же, наверное, какая-то история заказов или сделок? кроме того, цену на "заказ" или "корзину покупок" вроде как менеджер тоже изменить может, т.е какая-то сущность этому соответствует?
История — конечно есть. Но её реализация делается примерно одинаково примерно везде.
По поводу цены — ещё раз расскажу всё то же самое: менеджер "меняет" цену прямо в UI. Вот он получил список товаров с рекомендованными ценами, отобрал некоторые из них, цену переопределил — отправил заказ.
Всё. До "отправки заказа" никакого заказа нет. Даже управление в наш код не попало ещё. Нет никаких событий, и никаких подписчиков у этих событий.
S>>Пользователь сразу видит список товаров с их ценами. Скидки/надбавки ему не видны — это закрытые подробности. Всё, что он видит — конкретную сумму.
T>ну раз так, то тогда попытки прилепить логику подсчёта цены к "заказу" были неправильными, "заказ" отвечает лишь за подсчёт конечной цены, ну ещё и эта коррекция цены менеджером тоже где-то должна произойти
Ну, вот ждём ответа от DDD — где же будет вся эта логика. И где будут храниться сами параметры ценообразования.

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

T>1) либо денормализацией данных, так что каждый агрегат или предметная область получает в виде копии всю необходимую информацию (обычно путём подписки и отправки сообщений)
T>2) либо композицией логик разных предметных областей

T>первый путь , в принципе, легко реализуем, когда есть инфраструктура для обработки сообщений и всё решение сделано в ддд-стиле,

Ну, у нас пока размер задачи достаточно мелок, чтобы нарисовать всю "инфраструктуру" и всё "решение" с нуля в любом стиле. Пока что я совсем-совсем не понимаю ни первого ни второго пункта.
То есть — как именно устроена "подписка на события", кто их порождает, кто обрабатывает.
А если композиция логик — то как она выполняется. Если логика внутри агрегата — то какого агрегата? Если она вынесена — то куда? И не приведёт ли это к тому, что все изменения надо вносить в несколько мест — как в ту "вынесенную" логику, так и в каждый из "агрегатов", которые мы тут пытаемся идентифицировать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[8]: DDD для небольших проектов.
От: TG  
Дата: 14.02.20 09:28
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Я не понимаю, как описать систему, используя эти ваши принципы. Как описать систему, используя понятные мне принципы, я и так знаю — могу продемонстрировать.


Да, было бы интересно.
Re[8]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 14.02.20 09:34
Оценка: 76 (1)
Здравствуйте, Sinclair, Вы писали:

S>В общем, непонятно — как правильно-то?

Кстати на счет этого. Рекомендую https://pragprog.com/book/swdddf/domain-modeling-made-functional
Оно про f#, но применимо и к c#, если есть причины страдать на нем. Сильно помогает дать почитать первые главы продакт-овнерам и прочим аналитикам. После этого с ними разговаривать сильно проще.
Re[9]: DDD для небольших проектов.
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 09:44
Оценка:
Здравствуйте, Poopy Joe, Вы писали:
PJ>Критерий убедительности в студию.
Приведено решение с набросками кода.

PJ>Не понял контекст. Я ничего не говорил про стейт-машину, от слова совсем.

Это я перескакиваю через рассуждение. Весь документооборот можно описать при помощи стейт-машины. В любом магазине или системе заказов есть т.н. workflow — то самое описание состояний заказа, и действий, которые переводят заказ из состояния в состояние. Вы в своей работе Jira используете? Там можно ткнуть в любом артефакте на кнопочку "workflow" и он покажет схему переходов из состояния в состояние.
Можно это описывать машиной состояний. Машина состояний описывает правила перехода между этими состояниями. Например, если мы приделываем к заказу платёж, то
— заказ в состоянии "новый" переходит в состояние "оплаченный",
— заказ в состоянии "отправленный" переходит в состояние "отправленный, оплаченный"
— заказ в состоянии "доставленный" переходит в состояние "доставленный, оплаченный".
Это в случае, если у нас разрешена пост-оплата. В ней заказ в состоянии "собранный" при выполнении операции "передача в доставку" переходит в "отправленный".

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

PJ>Ну я бы предположил отправить заказчику. Но это ж твоя задача, тебе должны быть виднее какие еще есть состояния у заказа.

В реальных системах обработки заказов по 10-20 состояний, и они не исчерпывают возможное множество состояний. Это просто так нарулен конкретный workflow в конкретном магазине.
Завтра придёт новое требование — техник подправит правило в UI, и заказы будут ходить по-новому.

PJ>В моем мире нет заказов, поэтому меня не надо бомбардировать терминами "наложенным платежом", я без понятия в чем там отличие, но в остальном вот так и работает. Я бы хотел понять, что тут тебе кажется невозможным.

Заказ наложенным платежом — это когда товар отправляется по почте, а покупатель расплачивается при получении. Термин достаточно распространённый.

PJ>Ну покажи ее продолжение.

Я же показал вам 512 типов заказа, сколько ещё нужно?

PJ>Разумеется позволяет, что заставляет тебя думать обратное?

Отсутствие примера.

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


PJ>Что такое настоящий язык? Я их только по именам знаю.

Такой, программу на котором можно исполнить.

PJ>Где тут дублирование кода?

Давайте вы напишете код NewOrder, ShippedOrder, PaidOrder, и PaidShippedOrder. А то я решительно не понимаю, что вы имеете в виду. Вот вы написали, что в этих типах будут методы, "которые сохраняют инвариант модели". Поскольку инварианты у PaidOrder и PaidShippedOrder частично совпадают, то я не вижу нормального способа избежать дублирования кода.

PJ>Это не делат каждое его слово истиной. Так-то много кого почитать полезно. Вот тебе посоветовали почитать Влашина, но я не вижу у тебя энтузиазма.

Кто из них Влашин? Я всё, что мне давали текстового, прочитал. А тратить по 3 часа на просмотр скучных фильмов у меня возможности нет — мне ещё и работать надо.

PJ>На любом статически типизированом. Наиболее лаконично, разумеется, на функциональных языках. Однако прекрасно работает и на c++. Однако, лаконичность и натуральность это только бонус, большой бонус, да. Но, главное тут гарантии корректности.

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

PJ>Так хотя бы попробуй. Попытки уже и можно обсуждать. Если у тебя все сводится к "это невозможно потому, что Липперт так сказал", ну используй что можешь. Чего тут обсуждать-то?

(facepalm). Я и так использую то, что могу.

PJ>За фанатов не скажу, но по мне все это детали реализации. Изоляция от базы она совершенно натуральна. Модель зависит от себя самой (ну еще может быть shared kernels других BC), BL зависит от модели, AL зависит от BL и модели, Инфраструктура от всего остального, включая базую. Таким образом внедрить зависимость на базу в логику просто физически не получится. Красота.

Дьявол — в деталях.

PJ>Ну, можно конечно и так интепретировать. Я не думаю, что тут есть понятие "правильно". Важно что ты хочешь добиться. Вот лично я хочу добиться минимального времени отладки. И оно работатет, и не зависит от размера проекта.

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

А то примеры, которые в сети выдают за DDD, запросто оперируют штуками типа "ну, давайте мы тут материализуем весь прайслист да пробежимся по нему линейным поиском". Ну, в отладке-то наверное это ок, а в продакшне мы на каждый PUT-реквест делаем full table scan по потенциально безлимитному списку, а потом бегаем по этим мегабайтам алгоритмом Шлемиеля.
Нет, такой футбол нам не нужен.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[10]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 14.02.20 10:58
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Приведено решение с набросками кода.

Ну это как-то нечестно. Ты просишь код, а сам приводишь "наброски".

S>А она может быть как разрешена, так и запрещена. Если она запрещена, то операция "передача в доставку" для состояния "собранный" запрещена. Необходимо состояние "собранный, оплаченный".

S>То, что вы предлагаете, фактически представляет каждое состояние отдельным типом.

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

S>А что, если возможность пост-оплаты не является статической, а вычисляется динамически? Ну, там, кредит лимитом покупателя? Или страной доставки?

Какая разница? Это BL.

S>Нет, мы, наверное, можем попытаться выразить и это в рамках системы типов; но у меня подозрение, что кончится всё ровно тем, что валидными останутся более-менее все состояния, а правила будут проверяться внутри операций трансформации.

Разумеется. Инвариант это, к пример, ShippableOrder и его тупо нельзя создать без адреса потому что сигнатура ShippableOrder(ValidAddress address, ValidOrder order), как ты его создал раздешь ли бесплатно по акции или получил деньги не нарушает инвариант. Он shippable потому что у него есть все для это конкретной задачи и оно гарантировано валидно. Все остальное это твои бизнес-правила.

PJ>>Ну я бы предположил отправить заказчику. Но это ж твоя задача, тебе должны быть виднее какие еще есть состояния у заказа.

S>В реальных системах обработки заказов по 10-20 состояний, и они не исчерпывают возможное множество состояний. Это просто так нарулен конкретный workflow в конкретном магазине.
S>Завтра придёт новое требование — техник подправит правило в UI, и заказы будут ходить по-новому.
Так одно состояние может быть комплексным. Есть есть IncompleteOrder, то в этом состоянии заказ может оставать до тех пор пока он физически не сможет перейти в состояние Valid
допуcтим
namespace Model
{
    public class NewOrder
    {
        public NotEmptyList<Article> Articles { get; set; }    
        public Optional<Customer> Customer { get; set; }
    }
    
    public struct IncompleteOrder
    {
        public NotEmptyList<Article> Articles { get; set; }
        public Customer Customer { get; set; }
        public Optional<Address> Address{ get; set; }
        public Optional<StockReference> Reserved { get; set; } 
    }
    
    public class ValidOrder
    {
        public ValidOrder(NotEmptyList<Article> article, Address address, Customer customer, StockReference reference){}

        public NotEmptyList<Article> Articles { get; }
        public Address Address{ get; }
        public Customer Customer { get; }
        public Optional<StockReference> Reserved { get; } 
    }

    public class PaidOrder
    {
        public PaidOrder(ValidOrder order, PaymentInfo paymentInfo){}
    }

    public class UnpaidOrder
    {
        public UnpaidOrder(ValidOrder order, ExpectedPaymentInfo paymentInfo){}
    }

    public class ShippableOrder
    {
        public OneOf<PaidOrder, UnpaidOrder> Order {get;}
        public ShippableOrder(PaidOrder order){}
        public ShippableOrder(UnpaidOrder order){}
    }
    
    public class ShippedOrder
    {
        public ShippedOrder(ShippableOrder order){}
    } 

    public class DeliveredOrder
    {
        public DeliveredOrder(ShippedOrder order){}
    } 
    
}

Есть заказ, который по сути просто список позиций. Покупатель может быть залогинен, а может и нет. Но чтобы начать выполнение он должен быть залогинен. Поэтому поле Customer в IncompleteOrder и Valid уже не опциональное. Если все нужное есть, то получаем Valid, если нет, то Incomplete с пустыми полями там где нет данных. Нетрудно вывести, что именно надо запросить у пользователя или где-то еще. IncompleteOrder можно гонять по всей системе он совершенно безопасен, потому что послать можно только ShippableOrder, а что бы его получить надо получить Valid в котором опциональных полей нет. Тебе просто придется написать правильную бизнес-логику, чтобы это скомпилировалось. Каждый из типов отражает именно бизнес реальность. Нет никах 512ти типова заказа. В каждом отдельном случае логика совершенно однозначна и понятна, хотя может и потребовать каких-то оптимизаций, это совершенно неважно.

PJ>>Ну покажи ее продолжение.

S>Я же показал вам 512 типов заказа, сколько ещё нужно?
А я показал, что это не так.

PJ>>Где тут дублирование кода?

S>Давайте вы напишете код NewOrder, ShippedOrder, PaidOrder, и PaidShippedOrder. А то я решительно не понимаю, что вы имеете в виду. Вот вы написали, что в этих типах будут методы, "которые сохраняют инвариант модели". Поскольку инварианты у PaidOrder и PaidShippedOrder частично совпадают, то я не вижу нормального способа избежать дублирования кода.
Кода чего? Это просто данные. Модель.

PJ>>Это не делат каждое его слово истиной. Так-то много кого почитать полезно. Вот тебе посоветовали почитать Влашина, но я не вижу у тебя энтузиазма.

S>Кто из них Влашин? Я всё, что мне давали текстового, прочитал.
Тот который говорит про F#.

S>А тратить по 3 часа на просмотр скучных фильмов у меня возможности нет — мне ещё и работать надо.

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

PJ>>На любом статически типизированом. Наиболее лаконично, разумеется, на функциональных языках. Однако прекрасно работает и на c++. Однако, лаконичность и натуральность это только бонус, большой бонус, да. Но, главное тут гарантии корректности.

S>Ок, жду пример кода на статически типизированном C#. На с++ есть плохо портируемые в другие языки возможности, вроде параметризации шаблонов трейтами.
Прости, но я не будет писать систему обработки заказов только для того, чтобы, может быть, убедить тебя в чем-то. Даже странно такого ожидать. Примеры типов, примеры каких-то трансформаций можно, если вопрос конкретный. Но весь пример даже странно ожидать, если не ставится цель сказать "а, ну все понятно, не можешь..." Нет, не могу, у меня тоже свои дела есть.

S>А то примеры, которые в сети выдают за DDD, запросто оперируют штуками типа "ну, давайте мы тут материализуем весь прайслист да пробежимся по нему линейным поиском". Ну, в отладке-то наверное это ок, а в продакшне мы на каждый PUT-реквест делаем full table scan по потенциально безлимитному списку, а потом бегаем по этим мегабайтам алгоритмом Шлемиеля.

Это никакого отношения к DDD не имеет. Никто не запрещает далать любые оптимизации. DDD запрещает делать их из модели, поскольку они относятся к деталям бд, от которой не зависит бизнес-логика.
Отредактировано 14.02.2020 11:11 Poopy Joe . Предыдущая версия . Еще …
Отредактировано 14.02.2020 11:00 Poopy Joe . Предыдущая версия .
Re[32]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 14.02.20 11:14
Оценка:
Здравствуйте, Mystic Artifact, Вы писали:

S>>Чтобы отвязаться от DbContext напрямую.

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

Репозиторий нужен для аггрегатов, на сколько я понимаю. Т.е. для ключевых сущностей типа пользователь и т.д.
https://aspnetboilerplate.com/Pages/Documents/Repositories

Я про этот фреймворк уже спрашивал
Автор: Sharov
Дата: 24.01.20
. Мне дали его платную версию (asp.net zero), там куча всего сгенеренного типа авторизации, управления правами и т.д. и т.п.
Так вот все это завернуто в DDD.
Кодом людям нужно помогать!
Re[6]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 14.02.20 11:35
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Вот у нас девять бинарных признаков. В патологическом случае это даёт 2^9 возможных комбинаций, для каждой из которых потенциально нужен свой тип. Итого — 512 типов, от "пустой заказ" до "заказ оплачен и доставлен".

S>Понятно, что некоторые комбинации в природе не встречаются — мы можем свернуть это пространство во что-то более компактное.
S>Ну, например, заметив, что не бывает такого, чтобы заказ уехал в доставку, минуя фазу зарезервированности, мы можем превратить 9 бинарных признаков в 4 признака с бОльшим количеством значений.
S>Тем не менее, у нас по-прежнему количество типов растёт экспоненциально. И операций типа "отгрузить" у нас получается не одна, потому что отгрузка ReadyOrder переводит его в ShippedOrder, а отгрузка PaidReadyOrder переводит его в PaidShippedOrder.
S>В какой системе типов мы можем это отразить? На всякий случай я рекомендую к прочтению серию статей Эрика Липперта "о колдунах и воинах", https://ericlippert.com/2015/05/11/wizards-and-warriors-part-five/.

Тут напрашивается конечный автомат для сущности order.
Кодом людям нужно помогать!
Re[33]: DDD для небольших проектов.
От: Mystic Artifact  
Дата: 14.02.20 11:51
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Репозиторий нужен для аггрегатов, на сколько я понимаю. Т.е. для ключевых сущностей типа пользователь и т.д.

S>https://aspnetboilerplate.com/Pages/Documents/Repositories
Ну, тут они вроде как ставят конкретную цель — отвязаться от конкретного ORM. При этом они ещё добавляют IPersonAppService который в примере является адаптером/переходником для этого репозитория, в который наверное когда-нибудь будет положена некоторая логика, но в своей массе это pass-thru методы с маппингом.
Я очень сомневаюсь в необходимости репозиториев в таком виде: не над всеми сущностями разрешены все операции и т.д. и т.п. Если есть необходимость поддержки разных БД и/или разных ОРМ — не проще ли делать специфические "дата акцессоры" которые нужны прямо здесь и сейчас? Тем более все более-менее осмысленные запросы выходящие за пределы CRUD понадобится дописывать руками всё равно. Тем более набор полей для вставки, апдейта и чтения разный. Просто не существует никакого осмысленного Person в репозитории с полями 1-в-1 соответствующими БД.
Re[10]: DDD для небольших проектов.
От: MadHuman Россия  
Дата: 14.02.20 11:52
Оценка:
Здравствуйте, Sinclair, Вы писали:


PJ>>Это не делат каждое его слово истиной. Так-то много кого почитать полезно. Вот тебе посоветовали почитать Влашина, но я не вижу у тебя энтузиазма.

S>Кто из них Влашин? Я всё, что мне давали текстового, прочитал. А тратить по 3 часа на просмотр скучных фильмов у меня возможности нет — мне ещё и работать надо.
если незнакомы с подходом — как на дизайнить типы так, чтоб минимизировать возможности задавать невалидные состояния — очень рекомендую посмотреть эти видео.
в своё время, для меня это было не то чтоб открытие, но очень познавательно.

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

хотя бы это (на русском)
https://youtu.be/Bn132AtZLhc
Re[3]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 14.02.20 12:07
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

PJ>Если из него выкинуть ооп часть то все становиться сильно проще и логичнее.


Можно конкретнее зачам это надо и почему это лучше?

PJ>В третьем слое — application logic, собственное сами сценарии, как и когда что работает. По-хорошему, это просто композиция функций из предыдущего слоя. И последний слой это инфраструктура или порты, который связывает все это с внешним миром. Там живут все DbConnection и прочие файлы. Зависимости всегда направлены внутрь. Собственно и все. Легко тестируется, легко поддерживать, легко понять что происходит, сломать трудно. Отлично масштабируется. Если калькулятор, то это все что надо, если кровавый энтерпрайз, то это один из BC. Как-то так.


А dbconnection там не лишнаяя, это же к модели, которая явно "ниже" application service?
Кодом людям нужно помогать!
Re[34]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 14.02.20 12:10
Оценка:
Здравствуйте, Mystic Artifact, Вы писали:

S>>Репозиторий нужен для аггрегатов, на сколько я понимаю. Т.е. для ключевых сущностей типа пользователь и т.д.

S>>https://aspnetboilerplate.com/Pages/Documents/Repositories
MA> Ну, тут они вроде как ставят конкретную цель — отвязаться от конкретного ORM.

ORM и есть Dbcontext.

MA> При этом они ещё добавляют IPersonAppService который в примере является адаптером/переходником для этого репозитория, в который наверное когда-нибудь будет положена некоторая логика, но в своей массе это pass-thru методы с маппингом.


Это уже сервис, который использует репозиторий для доступа к модели.
Кодом людям нужно помогать!
Re[11]: DDD для небольших проектов.
От: Sinclair Россия http://corp.ingrammicro.com/Solutions/Cloud.aspx
Дата: 14.02.20 12:28
Оценка:
Здравствуйте, Poopy Joe, Вы писали:
PJ>Не каждое. Каждое которое можно отправить. Ну так оно и есть особое. Ты не можешь отправить и закрыть. Где-то надо сохранить, чтобы убедиться, что деньги получены. Это состояние в любом случае будет в твоей стейт-машине, иначе оно просто не будет правильно работать.
Отож.

PJ>Какая разница? Это BL.

Нууу, как же — большая разница.

PJ>Так одно состояние может быть комплексным. Есть есть IncompleteOrder, то в этом состоянии заказ может оставать до тех пор пока он физически не сможет перейти в состояние Valid

PJ>допуcтим
PJ>
PJ>namespace Model
PJ>{
PJ>    public class NewOrder
PJ>    {
PJ>        public NotEmptyList<Article> Articles { get; set; }    
PJ>        public Optional<Customer> Customer { get; set; }
PJ>    }
    
PJ>    public struct IncompleteOrder
PJ>    {
PJ>        public NotEmptyList<Article> Articles { get; set; }
PJ>        public Customer Customer { get; set; }
PJ>        public Optional<Address> Address{ get; set; }
PJ>        public Optional<StockReference> Reserved { get; set; } 
PJ>    }
    
PJ>    public class ValidOrder
PJ>    {
PJ>        public ValidOrder(NotEmptyList<Article> article, Address address, Customer customer, StockReference reference){}

PJ>        public NotEmptyList<Article> Articles { get; }
PJ>        public Address Address{ get; }
PJ>        public Customer Customer { get; }
PJ>        public Optional<StockReference> Reserved { get; } 
PJ>    }

PJ>    public class PaidOrder
PJ>    {
PJ>        public PaidOrder(ValidOrder order, PaymentInfo paymentInfo){}
PJ>    }

PJ>    public class UnpaidOrder
PJ>    {
PJ>        public UnpaidOrder(ValidOrder order, ExpectedPaymentInfo paymentInfo){}
PJ>    }

PJ>    public class ShippableOrder
PJ>    {
PJ>        public OneOf<PaidOrder, UnpaidOrder> Order {get;}
PJ>        public ShippableOrder(PaidOrder order){}
PJ>        public ShippableOrder(UnpaidOrder order){}
PJ>    }
    
PJ>    public class ShippedOrder
PJ>    {
PJ>        public ShippedOrder(ShippableOrder order){}
PJ>    } 

PJ>    public class DeliveredOrder
PJ>    {
PJ>        public DeliveredOrder(ShippedOrder order){}
PJ>    } 
    
PJ>}
PJ>

Прекрасно. Всё начинает проясняться.
PJ>Есть заказ, который по сути просто список позиций. Покупатель может быть залогинен, а может и нет. Но чтобы начать выполнение он должен быть залогинен. Поэтому поле Customer в IncompleteOrder и Valid уже не опциональное. Если все нужное есть, то получаем Valid, если нет, то Incomplete с пустыми полями там где нет данных. Нетрудно вывести, что именно надо запросить у пользователя или где-то еще. IncompleteOrder можно гонять по всей системе он совершенно безопасен, потому что послать можно только ShippableOrder, а что бы его получить надо получить Valid в котором опциональных полей нет. Тебе просто придется написать правильную бизнес-логику, чтобы это скомпилировалось. Каждый из типов отражает именно бизнес реальность. Нет никах 512ти типова заказа. В каждом отдельном случае логика совершенно однозначна и понятна, хотя может и потребовать каких-то оптимизаций, это совершенно неважно.
Ну, вот у нас уже получилось 8 типов заказа — ровно 2-в-степени-количество-степеней-свободы.
И эта модель у нас теряет информацию: после отправки у нас уже нет никаких данных о том, был ли заказ оплачен. Ну, то есть наверное можно попробовать это починить, добавив в ShippedOrder и DeliveredOrder свойство Order по образцу ShippableOrder. Но не факт, что этого достаточно: если мы хотим потребовать платёж-при-получении от наших Shippable(UnpaidOrder), то как будет выглядеть сигнатура конструктора DeliveredOrder?
Надо полагать, будет что-то типа
        public DeliveredOrder(ShippedPaidOrder order){} // тут paymentInfo и так есть
        public DeliveredOrder(ShippedUnpaidOrder order, PaymentInfo paymentInfo){} // а тут надо её предоставить

Может, я чего-то не понимаю, но у нас только что класс ShippedOrder развалился надвое. А заодно придётся развалить пополам и класс ShippableOrder.
Итого — уже 10 классов. И это мы ещё не начали думать о возвратах. Либо мы переносим данные из статического типа в динамическое состояние — как вы поступили с ShippableOrder.
Теперь, чтобы узнать, нуждается ли ShippableOrder в оплате, придётся делать рантайм-анализ:
var paymentInfo = (shippableOrder.Order is PaidOrder paidOrder) ? paidOrder.PaymentInfo : UI.RequestPaymentInfo();

PJ>А я показал, что это не так.
Пока что вы подтверждаете мои опасения.
S>>Давайте вы напишете код NewOrder, ShippedOrder, PaidOrder, и PaidShippedOrder. А то я решительно не понимаю, что вы имеете в виду. Вот вы написали, что в этих типах будут методы, "которые сохраняют инвариант модели". Поскольку инварианты у PaidOrder и PaidShippedOrder частично совпадают, то я не вижу нормального способа избежать дублирования кода.
PJ>Кода чего? Это просто данные. Модель.
Ну как же. Вот у вас почти у всех ордеров есть конструкторы. Вы их так пишете, как будто достаточно перечислить аргументы, и всё заработает. Ну дак ведь нет — надо же весь этот бойлерплейт писать руками:
public ValidOrder(NotEmptyList<Article> articles, Address address, Customer customer, StockReference reference)
{
  Articles = articles ?? throw new ArgumentNullException(nameof(articles));
  Address = address ?? throw new ArgumentNullException(nameof(address));
  Customer = customer ?? throw new ArgumentNullException(nameof(customer));
  Reserved = reference ?? throw new ArgumentNullException(nameof(reference)); 
}

Это, ессно, в предположении, что сами NotEmptyList<T>, Address, Customer, и StockReference прилагают аналогичные усилия при конструировании, чтобы не дать присвоить булшит.
При этом компилятор всё ещё ни за чем из этого не следит — я могу запросто забыть проинициализировать свойство в конструкторе, и оно прекрасно останется null. В итоге код компилируется, хотя и падает при первом же юнит тесте.
S>>Кто из них Влашин? Я всё, что мне давали текстового, прочитал.
PJ>Тот который говорит про F#.
Ок, почитаем. Выглядит многообещающе.

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

К сожалению, без кода вести технические дискуссии бессмысленно.

PJ>Это никакого отношения к DDD не имеет. Никто не запрещает далать любые оптимизации. DDD запрещает делать их из модели, поскольку они относятся к деталям бд, от которой не зависит бизнес-логика.

Ну ок. Модель — это, вроде бы, entities. Агрегаты в вашей версии DDD есть? Или нету?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
http://rsdn.org/File/5743/rsdnaddict.GIF
Re[35]: DDD для небольших проектов.
От: Mystic Artifact  
Дата: 14.02.20 12:36
Оценка:
Здравствуйте, Sharov, Вы писали:

S>ORM и есть Dbcontext.

Да я как бы в курсе.

S>Это уже сервис, который использует репозиторий для доступа к модели.

Т.е. если откинуть попытки универсальности — можем спокойно из PersonAppService спокойно использовать DbContext.

И если пойти дальше — вынести/сгруппировать операции над БД (я такое практиковал для целей тестирования и последующей адаптации к реальной БД) в соотв классах/интерфейсах — получим почти тот же репозиторий.

Только это больше похоже на набор "транзакшн скриптов", нежели на репозиторий, т.к. нет никаких реальных ограничений или разделений. Репозитории провоцируют группировку по сущностям, но это не всегда оправдано. Системы всегда оперируют с несколькими сущностями, и это группируется (на мой взгляд только логически/по функционалу), но ни как по эфимерным сущностям.
Re[36]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 14.02.20 14:00
Оценка:
Здравствуйте, Mystic Artifact, Вы писали:

S>>Это уже сервис, который использует репозиторий для доступа к модели.

MA> Т.е. если откинуть попытки универсальности — можем спокойно из PersonAppService спокойно использовать DbContext.

Типа не по правилам -- https://raw.githubusercontent.com/aspnetboilerplate/aspnetboilerplate/master/doc/WebSite/images/abp-nlayer-architecture.png
Там между ORM и app. srv. есть domain layer.
Кодом людям нужно помогать!
Re[12]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 14.02.20 14:05
Оценка: 12 (1)
Здравствуйте, Sinclair, Вы писали:

PJ>>Какая разница? Это BL.

S>Нууу, как же — большая разница.
Очень информативно.

S>Ну, вот у нас уже получилось 8 типов заказа — ровно 2-в-степени-количество-степеней-свободы.

Тут ровно две степени сводобы оплачен или нет. Все остальные типы завсисимые.

S>И эта модель у нас теряет информацию: после отправки у нас уже нет никаких данных о том, был ли заказ оплачен. Ну, то есть наверное можно попробовать это починить, добавив в ShippedOrder и DeliveredOrder свойство Order по образцу ShippableOrder. Но не факт, что этого достаточно: если мы хотим потребовать платёж-при-получении от наших Shippable(UnpaidOrder), то как будет выглядеть сигнатура конструктора DeliveredOrder?

Это все зависит от того как это системой обрабатывается. Можно при создании ShippableOrder создавать еще и PaymentRequest и обрабатывать его отдельно, позваляя, например, переводить деньги на счет. С собственно доставкой эта операция не больно-то связана. Не обязательно все валить в одну кучу.

S>Надо полагать, будет что-то типа

S>
S>        public DeliveredOrder(ShippedPaidOrder order){} // тут paymentInfo и так есть
S>        public DeliveredOrder(ShippedUnpaidOrder order, PaymentInfo paymentInfo){} // а тут надо её предоставить
S>

S>Может, я чего-то не понимаю, но у нас только что класс ShippedOrder развалился надвое. А заодно придётся развалить пополам и класс ShippableOrder.
В каком, прости, месте? И в том, и в другом случае ты создаешь тип одын штука.

S>Итого — уже 10 классов. И это мы ещё не начали думать о возвратах. Либо мы переносим данные из статического типа в динамическое состояние — как вы поступили с ShippableOrder.

Да хоть 110. Ты это говоришь с таким придыханим, как будь-то у тебя количество типов чем-то ограничено. А куда ты поместишь код когда начнешь думать о возвратах? В астрал?

S>Теперь, чтобы узнать, нуждается ли ShippableOrder в оплате, придётся делать рантайм-анализ:

S>
S>var paymentInfo = (shippableOrder.Order is PaidOrder paidOrder) ? paidOrder.PaymentInfo : UI.RequestPaymentInfo();
S>

Детали реализации. Может да, может нет. Никто не мешает имееть структуру Tuple<ShippedOrder, PaymentRequest> Все эти типы можно как угодно массировать до получения полного удовлетворения и уверенност, что вот сейчас все так как ты и хотел.

PJ>>А я показал, что это не так.

S>Пока что вы подтверждаете мои опасения.
Разве что конструкторы считать "разваливанием класса на два"

S>Ну как же. Вот у вас почти у всех ордеров есть конструкторы. Вы их так пишете, как будто достаточно перечислить аргументы, и всё заработает. Ну дак ведь нет — надо же весь этот бойлерплейт писать руками:

S>
S>public ValidOrder(NotEmptyList<Article> articles, Address address, Customer customer, StockReference reference)
S>{
S>  Articles = articles ?? throw new ArgumentNullException(nameof(articles));
S>  Address = address ?? throw new ArgumentNullException(nameof(address));
S>  Customer = customer ?? throw new ArgumentNullException(nameof(customer));
S>  Reserved = reference ?? throw new ArgumentNullException(nameof(reference)); 
S>}
S>

Не надо. C# поддерживает nullable reference types.
Если ты хочешь наехать на бойлерплейт в C#, то есть куда более болезенные места. Например, дефолтный equality comparer, который проверяет ссылку, отсутствие ADT, нормальной композии и убогий паттерн-матчинг.
Но так все это одинаково справедливо хоть с DDD, хоть без. C# это путь боли. Но это ж добровольный выбор.

S>При этом компилятор всё ещё ни за чем из этого не следит — я могу запросто забыть проинициализировать свойство в конструкторе, и оно прекрасно останется null. В итоге код компилируется, хотя и падает при первом же юнит тесте.

Следит. Хотя пока в виде варнинга, но варнинги можно и как ошибки поставить.

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

S>К сожалению, без кода вести технические дискуссии бессмысленно.
Уж точно бессмысленно требовать от других, не показывая свой. Гораздо больше хочется помочь человеку, который действительно прилагает усилия понять и что-то сделать.

PJ>>Это никакого отношения к DDD не имеет. Никто не запрещает далать любые оптимизации. DDD запрещает делать их из модели, поскольку они относятся к деталям бд, от которой не зависит бизнес-логика.

S>Ну ок. Модель — это, вроде бы, entities. Агрегаты в вашей версии DDD есть? Или нету?
Эээ...?! Модель это модель. В DDD есть value objects, то у чего нет ID. Адрес например. Entities — то, у чего есть ID. Иначе говоря разные инстансы могут ссылаться на одну entity. И агрегаты — коллекции entities. В данном пример customer, article это, очевидно, ссылки на них. Так что order это агрегат и есть.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.