DDD для небольших проектов.
От: Sharov Россия  
Дата: 07.02.20 11:40
Оценка:
Здравствуйте.

А не подскажет ли кто границы применимости DDD для проектов? Он хорошо ложится на проект любого масштаба или только для кровавого энтерпрайза?
Допустим для приложения типа калькулятор оно как, подойдет или слишком сложно будет? А для приложения где есть, скажем, 10-20 таблиц (сущностей) DDD
нормально будет? Или он только хорош, когда у нас есть n (например, n=100) сущностей?
Кодом людям нужно помогать!
Re: DDD для небольших проектов.
От: Vladek Россия Github
Дата: 07.02.20 13:59
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте.


S>А не подскажет ли кто границы применимости DDD для проектов? Он хорошо ложится на проект любого масштаба или только для кровавого энтерпрайза?

S>Допустим для приложения типа калькулятор оно как, подойдет или слишком сложно будет? А для приложения где есть, скажем, 10-20 таблиц (сущностей) DDD
S>нормально будет? Или он только хорош, когда у нас есть n (например, n=100) сущностей?

Таблицы есть в базах данных, а не приложениях... DDD — это, в сущности, модель предметной области. Людям, привыкшим мыслить схемами таблиц и хранимками, в любом масштабе будет казаться ненужной хренью.
Re[2]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 07.02.20 14:05
Оценка:
Здравствуйте, Vladek, Вы писали:


V>Таблицы есть в базах данных, а не приложениях...


Это был пример для объема приложения, объема предментно области.

V>DDD — это, в сущности, модель предметной области. Людям, привыкшим мыслить схемами таблиц и хранимками, в любом масштабе будет казаться ненужной хренью.


Для калькулятора DDD применим?
Кодом людям нужно помогать!
Re[3]: DDD для небольших проектов.
От: RushDevion Россия  
Дата: 07.02.20 14:30
Оценка: 4 (1)
DDD — это не столько про классы, таблицы и размеры проекта, сколько про единый язык для заказчиков (бизнеса) и программистов (тот самый Ubiquitous Language).
Т.е. если у тебя закодирован алгоритм реализующий какой-то бизнес-процесс, ты показываешь его, скажем бухгалтеру, и он может прочитать и понять о чем идет речь — это хороший DDD.
А если у тебя там абстрактная фабрика, создающая в шаблонном методе декораторы стратегии через билдер, то это может быть вполне годный ООП-дизайн, но плохой DDD.
Re[4]: DDD для небольших проектов.
От: AndrewJD США  
Дата: 07.02.20 16:58
Оценка:
Здравствуйте, RushDevion, Вы писали:

RD>DDD — это не столько про классы, таблицы и размеры проекта, сколько про единый язык для заказчиков (бизнеса) и программистов (тот самый Ubiquitous Language).

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

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

Вам приходилось такое делать на практике?
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Re[5]: DDD для небольших проектов.
От: RushDevion Россия  
Дата: 08.02.20 21:12
Оценка:
RD>>Т.е. если у тебя закодирован алгоритм реализующий какой-то бизнес-процесс, ты показываешь его, скажем бухгалтеру, и он может прочитать и понять о чем идет речь — это хороший DDD.
AJD>Вам приходилось такое делать на практике?

Нет, такого опыта у меня лично не было.
Однако это ровно то, что пропагандируется в DDD-шной литературе.
Т.е. вместо классической цепочки бизнес -> (хотелки, деньги) -> доменные эксперты -> аналитик -> (requirements) -> архитектор -> (design document) -> программисты -> код.
Нам предлагают посадить доменных экспертов в одну комнату с программистами и чтобы они через общение рожали shared mental model системы.
А выражением этой модели как раз и является код (или скорее псевдо-код), описывающий бизнес-процесс.
Re[3]: DDD для небольших проектов.
От: Vladek Россия Github
Дата: 09.02.20 12:03
Оценка: +1
Здравствуйте, Sharov, Вы писали:

S>Для калькулятора DDD применим?


Если есть задача разработать ядро программы, независимое от деталей реализации типа UI (веб, десктоп, консоль, веб-сервис) и БД (файлы, серверы, облака), то я буду ваять в стиле похожем на DDD (на практике это обычный ООП, ведомый здравым смысолом).
Re[4]: DDD для небольших проектов.
От: varenikAA  
Дата: 10.02.20 01:35
Оценка: 5 (1)
Здравствуйте, Vladek, Вы писали:

V>Здравствуйте, Sharov, Вы писали:


S>>Для калькулятора DDD применим?


V>Если есть задача разработать ядро программы, независимое от деталей реализации типа UI (веб, десктоп, консоль, веб-сервис) и БД (файлы, серверы, облака), то я буду ваять в стиле похожем на DDD (на практике это обычный ООП, ведомый здравым смысолом).


Ну, не совсем, DDD идет бок о бок с понятием Onion Archicture.
В этом случае Domain Model — отдельный проект полностью описывающий функционал системы со своим набором состоянии — валидных Ok(value) и невалидных Err(message).
Все остальное при этом (IU, DAL и прочее) превращается (легким движением) в инфраструктурный(обслуживающий) код.
DDD по сути есть полная противоположность спагетти-коду.
ФП прекрасно ложится на ДДД.
pdf можно загуглить в хорошем качестве. Да и на сайте есть неплохие примеры.

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

Если проект маленький то это имеет смысл даже больше, т.к. у вас есть шанс встать на светлую сторону.
Ведь чем больше проект тем сложнее придерживаться правильного пути. Начинают сильно мешать поставленные сроки и нечеткость ТЗ (каковая всегда имеет место быть).
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.02.20 14:27
Оценка: +4
Здравствуйте, Sharov, Вы писали:

S>нормально будет? Или он только хорош, когда у нас есть n (например, n=100) сущностей?

Я вообще хотел бы посмотреть на применение DDD к какому-нибудь небольшому и понятному проекту. Всё, что я видел из практики до сих пор — это кровавые слёзы, убедительно доказывающие "так делать не надо".
Вот, прямо сейчас пилю информационную архитектуру для простенькой системы онлайн-заказов. Вроде бы почти всё уже в голове сложилось, но мучают сомнения:
1. А правильно ли я всё придумал
2. Как к этому правильному прийти через DDD

Вкратце: в домене (по описаниям отдела продаж) есть
— товары
— группы товаров (товар входит в 1, 2 или 3 группы)
— реселлеры (продавцы)
— позиции прайслиста. Каждая позиция сопоставляет "базовую цену" одному товару.
— заказы. Заказ — как обычно: продавец, покупатель, 1..N позиций: (товар, количество, отпускная цена).
Наша задача — сделать правильную накрутку на базовую цену.
Работать должно примерно так:
— на все товары мы делаем фиксированную накрутку. Например, 4%. Или 7%.
— могут быть исключения для некоторых групп товаров — там, где нам поставщик даёт слишком маленькую скидку, мы хотим урезать наценку, иначе цена выходит дороже розницы
— могут быть исключения для некоторых продавцов — на их заказы мы хотим накручивать меньше.
— могут быть исключения для некоторых продавцов и товаров — по тем же причинам.
— иногда мы можем захотеть сделать скидку на конкретный заказ. Но это только если мы сами размещаем заказ от имени продавца. А когда они сами — см. требования выше.

Вот мне интересно, какие объекты будут появляться в DDD, и как они будут работать. Будет ли rich object model, или анемика?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 10.02.20 14:48
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>>нормально будет? Или он только хорош, когда у нас есть n (например, n=100) сущностей?

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


А не проще ли взять что-то готовое и адаптировать под себя, этого добра написано-переписано+всякие crm.
Кодом людям нужно помогать!
Re[3]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.02.20 16:35
Оценка:
Здравствуйте, Sharov, Вы писали:

S>А не проще ли взять что-то готовое и адаптировать под себя, этого добра написано-переписано+всякие crm.

Коротко: нет.
То, что мы пилим, это крошечная часть огромной системы. Которая интегрирует между собой сотни ещё более огромных систем.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: DDD для небольших проектов.
От: takTak  
Дата: 10.02.20 16:54
Оценка: 5 (1) +1
S>>А не проще ли взять что-то готовое и адаптировать под себя, этого добра написано-переписано+всякие crm.
S>Коротко: нет.
S>То, что мы пилим, это крошечная часть огромной системы. Которая интегрирует между собой сотни ещё более огромных систем.

рекомендую посмотреть выступления джимми богарда

https://www.youtube.com/watch?v=XxCA2VitFcQ

https://www.youtube.com/watch?v=_HkCMrbw1cA&t=843s

https://www.youtube.com/watch?v=SUiWfhAhgQw
Отредактировано 10.02.2020 16:58 takTak . Предыдущая версия .
Re[2]: DDD для небольших проектов.
От: AndrewJD США  
Дата: 10.02.20 18:25
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Я вообще хотел бы посмотреть на применение DDD к какому-нибудь небольшому и понятному проекту. Всё, что я видел из практики до сих пор — это кровавые слёзы, убедительно доказывающие "так делать не надо".


Да, хотелось бы увидеть презентацию архитектуры какого-нибудь продукта в котором DDD применялся бы так-то и так-то. Пока есть только куча презентаций всяких евангелистов, а не инженеров создателей реальных систем.
Опять, же все ссылаются на книгу Эванса. А кто он такой этот Эванс? Что он создал реального, кроме книги и хайпа вокруг нее?
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Re[3]: DDD для небольших проектов.
От: Qulac Россия  
Дата: 10.02.20 18:52
Оценка:
Здравствуйте, AndrewJD, Вы писали:

AJD>Здравствуйте, Sinclair, Вы писали:


S>>Я вообще хотел бы посмотреть на применение DDD к какому-нибудь небольшому и понятному проекту. Всё, что я видел из практики до сих пор — это кровавые слёзы, убедительно доказывающие "так делать не надо".


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

AJD>Опять, же все ссылаются на книгу Эванса. А кто он такой этот Эванс? Что он создал реального, кроме книги и хайпа вокруг нее?

Я где-то видел настоящий проект на ddd, сервис заказов билетов кажется. Вообще лучше попробовать самому это методику, так быстрей можно разобраться.
Программа – это мысли спрессованные в код
Re[5]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 11.02.20 05:24
Оценка: 1 (1) +3
Здравствуйте, takTak, Вы писали:
T>рекомендую посмотреть выступления джимми богарда
Вы шутите. Смотреть видео по техническим вопросам?
Да ещё и по часу длиной? У меня нет столько свободного времени, чтобы наблюдать, как человек в течение часа рассказывает то, что я могу прочесть за 8-9 минут.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: DDD для небольших проектов.
От: takTak  
Дата: 11.02.20 05:31
Оценка:
T>>рекомендую посмотреть выступления джимми богарда
S>Вы шутите. Смотреть видео по техническим вопросам?
S>Да ещё и по часу длиной? У меня нет столько свободного времени, чтобы наблюдать, как человек в течение часа рассказывает то, что я могу прочесть за 8-9 минут.

так DDD- это о том, что нужно говорить с заказчиком, на общение с заказчиком часами тоже нет времени? просто тот докладчик практику применения ddd в свои проектах там и демонстрирует...
Re[4]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 11.02.20 05:31
Оценка:
Здравствуйте, Qulac, Вы писали:
Q>Я где-то видел настоящий проект на ddd, сервис заказов билетов кажется. Вообще лучше попробовать самому это методику, так быстрей можно разобраться.
Ну давайте попробуем. Я вот привёл микро-пример, который лично у меня вызывает затруднения.
Обратите внимание — я вижу пробуксовку уже на уровне informational architecture, т.е. номенклатуры видимых пользователю "штук". Понятно, что внутреннее устройство приложения может отличаться от ментальной модели, которую мы навязываем пользователю (ну, там, в визарде у пользователя возникает ощущение движения вперёд-назад, а на самом деле это один цикл message pump с какими-то if-ами внутри)
Если я правильно осознал основу DDD — это то, что иерархия классов и взаимосвязь объектов в приложении у нас в итоге совпадёт с informational architecture.

Почему затруднения? Потому, что когда я пришёл в проект, версия 1 уже была выпущена. Т.е. парни спроектировали навигацию и весь UI в соответствиями с требованиями клиентов (ну, как им казалось).
И теперь клиенты плачут и есть кактус не хотят. Вот, мне интересно — DDD помог бы тем парням, которые пилили этот кактус до меня, сделать всё верно с первого раза?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[7]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 11.02.20 05:34
Оценка:
Здравствуйте, takTak, Вы писали:
T>так DDD- это о том, что нужно говорить с заказчиком, на общение с заказчиком часами тоже нет времени? просто тот докладчик практику применения ddd в свои проектах там и демонстрирует...
Я уже пообщался с заказчиком. Чтобы понять, есть ли вообще польза от учебных пособий, мне в первую очередь надо понять — есть ли примеры, похожие на мой. Если есть — то можно посмотреть, какие решения получаются, сравнить со своими, и потом уже делать backtrack к тому ходу рассуждений, который к этим решениям привёл.
В текстовом виде это понятно как делать. Как это делать с видео — непонятно совсем. Три часа подряд смотреть несмешной стендап, чтобы убедиться в том, что он не подходит — нет времени. Если бы была уверенность в том, что там есть ответы на все мои вопросы, то и четырёх часов не жалко. Но ведь нет её
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[8]: DDD для небольших проектов.
От: takTak  
Дата: 11.02.20 05:38
Оценка:
T>>так DDD- это о том, что нужно говорить с заказчиком, на общение с заказчиком часами тоже нет времени? просто тот докладчик практику применения ddd в свои проектах там и демонстрирует...
S>Я уже пообщался с заказчиком. Чтобы понять, есть ли вообще польза от учебных пособий, мне в первую очередь надо понять — есть ли примеры, похожие на мой. Если есть — то можно посмотреть, какие решения получаются, сравнить со своими, и потом уже делать backtrack к тому ходу рассуждений, который к этим решениям привёл.
S>В текстовом виде это понятно как делать. Как это делать с видео — непонятно совсем. Три часа подряд смотреть несмешной стендап, чтобы убедиться в том, что он не подходит — нет времени. Если бы была уверенность в том, что там есть ответы на все мои вопросы, то и четырёх часов не жалко. Но ведь нет её

для таких целей того же чувака, который с использованием ddd работал, нанимают на пару недель или месяцев: или ты думаешь, что кто-то бесплатно будет тебе рассказывать то, что именно тебе нужно?
Re: DDD для небольших проектов.
От: GarryIV  
Дата: 11.02.20 05:44
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте.


S>А не подскажет ли кто границы применимости DDD для проектов? Он хорошо ложится на проект любого масштаба или только для кровавого энтерпрайза?

S>Допустим для приложения типа калькулятор оно как, подойдет или слишком сложно будет? А для приложения где есть, скажем, 10-20 таблиц (сущностей) DDD
S>нормально будет? Или он только хорош, когда у нас есть n (например, n=100) сущностей?

Потреннироваться нормально, выгоды не будет.
WBR, Igor Evgrafov
Re[9]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 11.02.20 05:53
Оценка: +1
Здравствуйте, takTak, Вы писали:
T>для таких целей того же чувака, который с использованием ddd работал, нанимают на пару недель или месяцев: или ты думаешь, что кто-то бесплатно будет тебе рассказывать то, что именно тебе нужно?
Конечно. Для этого и нужны форумы.
Мне вот почему-то нетрудно на пальцах показать, как проектируется anemic data model, или как сделать REST для сервиса, который на первый взгляд работает только через RPC-style.
А DDD, оказывается, требует чудовищных затрат даже на то, чтобы обсудить крошечный пример.
Стоит ли его рассматривать дальше, или его стоимость для задач реального масштаба будет вообще неподъемной?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[10]: DDD для небольших проектов.
От: takTak  
Дата: 11.02.20 06:09
Оценка:
T>>для таких целей того же чувака, который с использованием ddd работал, нанимают на пару недель или месяцев: или ты думаешь, что кто-то бесплатно будет тебе рассказывать то, что именно тебе нужно?
S>Конечно. Для этого и нужны форумы.
S>Мне вот почему-то нетрудно на пальцах показать, как проектируется anemic data model, или как сделать REST для сервиса, который на первый взгляд работает только через RPC-style.
S>А DDD, оказывается, требует чудовищных затрат даже на то, чтобы обсудить крошечный пример.
S>Стоит ли его рассматривать дальше, или его стоимость для задач реального масштаба будет вообще неподъемной?

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

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

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

но, по крайней мере, при подобном подходе с заказчиком хотя бы часто и регулярно общались, чего , как правило, не происходит, ни при одном айти-проекте вплоть до того момента, как показывают уже "готовый" продукт
Re[11]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 11.02.20 06:41
Оценка:
Здравствуйте, takTak, Вы писали:

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

Если на пальцах, то мы говорим об ортогональных вещах. DDD — это дизайн; REST — это реализация.
T>ведь ясно же, что для совсем рудиментарных вещей подобным не занимаются...
Давайте сначала попробуем применить DDD для рудиментарной вещи. Если он даже для неё не подходит, то как мы будем применять его к реальным задачам?

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

Вот вы сейчас, простите, очень похожи на продавца snake oil. При наличии компетентных разработчиков и доступного заказчика любая методология имеет риск привести к хорошей реализации.
T>но, по крайней мере, при подобном подходе с заказчиком хотя бы часто и регулярно общались, чего , как правило, не происходит, ни при одном айти-проекте вплоть до того момента, как показывают уже "готовый" продукт
По-моему, вы путаете DDD с Agile. Аgile как раз во главу угла ставит возможность интервьюировать "представителя заказчика" в процессе разработки. Но Agile не навязывет никакого конкретного подхода к дизайну — он может быть DDD, может быть TDD, можно рожать Anemic Object Model, можно рожать Rich Object Model.

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

Вот я и предлагаю кому-то, кто хорошо разобрался в DDD, провести мастеркласс на маленьком изолированном примере. Вот он я — "представитель заказчика". Могу приходить в этот тред несколько раз в неделю, отвечать на вопросы.
Первичное интервью было уже проведено (упомянутые мной объекты предметной области описаны в документах, датированных 2016 годом).
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: DDD для небольших проектов.
От: takTak  
Дата: 11.02.20 06:57
Оценка:
T>>хорошо, вот давай на пальцах... что дешевле: потратить несколько месяцев на имплементацию рест-архитектуры, чтобы по прошествии нескольких месяцев понять, что ни одного клиента внутренняя архитектура приложений не интересует?! или всё таки лучше формализованно провести несколько дней с клиентом, понять, какие события на уровне бизнес-логики происходят, поделить эти события на кластеры независимых друг от друга событий, слабо или сильно связанных событий, разделив тем самым приложение на несколько независимых предметных сфер, и начать в соответствии с этими проектировать отдельные предметные сферы, пользуясь языком заказчика для отражения той модели, которая у заказчика в голове?!
S>Если на пальцах, то мы говорим об ортогональных вещах. DDD — это дизайн; REST — это реализация.
T>>ведь ясно же, что для совсем рудиментарных вещей подобным не занимаются...
S>Давайте сначала попробуем применить DDD для рудиментарной вещи. Если он даже для неё не подходит, то как мы будем применять его к реальным задачам?

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

S>Вот вы сейчас, простите, очень похожи на продавца snake oil. При наличии компетентных разработчиков и доступного заказчика любая методология имеет риск привести к хорошей реализации.
T>>но, по крайней мере, при подобном подходе с заказчиком хотя бы часто и регулярно общались, чего , как правило, не происходит, ни при одном айти-проекте вплоть до того момента, как показывают уже "готовый" продукт
S>По-моему, вы путаете DDD с Agile. Аgile как раз во главу угла ставит возможность интервьюировать "представителя заказчика" в процессе разработки. Но Agile не навязывет никакого конкретного подхода к дизайну — он может быть DDD, может быть TDD, можно рожать Anemic Object Model, можно рожать Rich Object Model.

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


S>Вот я и предлагаю кому-то, кто хорошо разобрался в DDD, провести мастеркласс на маленьком изолированном примере. Вот он я — "представитель заказчика". Могу приходить в этот тред несколько раз в неделю, отвечать на вопросы.

S>Первичное интервью было уже проведено (упомянутые мной объекты предметной области описаны в документах, датированных 2016 годом).

DDD ни к каким технологиям или реализациям не привязан, хотя его часто используют с CQRS & messaging queuing

не вижу никакого противоречия между DDD vs Agile, никто не запрещает использовать это вместе: просто это намного удобнее, когда всегда где-то недалеко есть человек, у кого можно спросить, как же оно нужно, а не ждать целого месяца непонятно чего

да, интересно, как это без формализованного опроса заказчика и без постоянного общения должно возникнуть общее понимание предметной области...
Re[13]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 11.02.20 07:22
Оценка: +1
Здравствуйте, takTak, Вы писали:
T>не вижу никакого противоречия между DDD vs Agile, никто не запрещает использовать это вместе: просто это намного удобнее, когда всегда где-то недалеко есть человек, у кого можно спросить, как же оно нужно, а не ждать целого месяца непонятно чего
Это не противоречие — просто вы почему-то подменяете одно другим.
T>да, интересно, как это без формализованного опроса заказчика и без постоянного общения должно возникнуть общее понимание предметной области...
Зачем вы уводите дискуссию в сторону?
На конкретный вопрос "как применить DDD к маленькой модельной задаче" вы сначала предлагаете потратить 3 часа на просмотр видео; потом пишете, что DDD сам по себе ничему не помогает; потом, что DDD требует постоянного присутствия представителя заказчика на месте разработки, и при этом он ещё и должен корректировать архитектурные ошибки, сделанные командой.

Изо всего этого решительно непонятна ни суть подхода DDD, ни его отличия от других подходов к дизайну, ни преимущества. Хотелось бы какой-то конкретики, а не вот этой воды.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[14]: DDD для небольших проектов.
От: takTak  
Дата: 11.02.20 07:35
Оценка:
T>>не вижу никакого противоречия между DDD vs Agile, никто не запрещает использовать это вместе: просто это намного удобнее, когда всегда где-то недалеко есть человек, у кого можно спросить, как же оно нужно, а не ждать целого месяца непонятно чего
S>Это не противоречие — просто вы почему-то подменяете одно другим.
T>>да, интересно, как это без формализованного опроса заказчика и без постоянного общения должно возникнуть общее понимание предметной области...
S>Зачем вы уводите дискуссию в сторону?
S>На конкретный вопрос "как применить DDD к маленькой модельной задаче" вы сначала предлагаете потратить 3 часа на просмотр видео; потом пишете, что DDD сам по себе ничему не помогает; потом, что DDD требует постоянного присутствия представителя заказчика на месте разработки, и при этом он ещё и должен корректировать архитектурные ошибки, сделанные командой.

S>Изо всего этого решительно непонятна ни суть подхода DDD, ни его отличия от других подходов к дизайну, ни преимущества. Хотелось бы какой-то конкретики, а не вот этой воды.


я просто не вижу какой-то иной возможности понять то, что нужно абстрактному заказчику, без постоянного общения с ним...

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

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

предметные области напрямую между собой не связаны

что этим достигается: loose coupling and high cohesion, причём в рамках логики заказчика, а не какого-то программиста васи, который сам себе что-то придумал, если приходит требование заказчика: а вот прикрутите плюшку Х к сфере системы скидок, то программист вася смотрит: где же предметная сфера скидок и там добавляет то, что нужно, не ломая ни сферу заказов ни маркетинговый модуль
Re[15]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 11.02.20 07:58
Оценка:
Здравствуйте, takTak, Вы писали:

T>я просто не вижу какой-то иной возможности понять то, что нужно абстрактному заказчику, без постоянного общения с ним...

Что вам мешает понять, что нужно абстрактному заказчику, проведя однократный (пусть и длинный) сеанс общения с ним в начале проекта?

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

Давайте вернёмся на 12 уровней выше. Смотрите, вот на что вы отвечаете: https://rsdn.org/forum/design/7654465.1
Автор: Sinclair
Дата: 10.02.20

Тут уже описана предметная область.
T>потом уже тактика: внутри предметной области определяются aggregates, они отвечают за всё : валидацию, обработку событий и т.д.
Ну, вы можете определить aggregates внутри предметной области, которую я описал в сообщении https://rsdn.org/forum/design/7654465.1
Автор: Sinclair
Дата: 10.02.20
?
T>что этим достигается: loose coupling and high cohesion, причём в рамках логики заказчика, а не какого-то программиста васи, который сам себе что-то придумал, если приходит требование заказчика: а вот прикрутите плюшку Х к сфере системы скидок, то программист вася смотрит: где же предметная сфера скидок и там добавляет то, что нужно, не ломая ни сферу заказов ни маркетинговый модуль
Давайте сначала применим эти принципы высокой морали к конкретной простой задаче, а потом уже посмотрим, что ими достигается. Потому что в абстрактных рассуждениях-то у всех loose coupling, high cogesion, и прочие maximizing the stakeholders value. Без конкретных приёмов и решений это просто bullshit.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[16]: DDD для небольших проектов.
От: takTak  
Дата: 11.02.20 09:03
Оценка: 5 (1)
S>Давайте сначала применим эти принципы высокой морали к конкретной простой задаче, а потом уже посмотрим, что ими достигается. Потому что в абстрактных рассуждениях-то у всех loose coupling, high cogesion, и прочие maximizing the stakeholders value. Без конкретных приёмов и решений это просто bullshit.

ну а немного погуглить-то тоже времени нет?
вот: https://hackernoon.com/making-a-case-for-domain-modeling-17cf47030732
https://softwareengineering.stackexchange.com/questions/129305/should-i-put-calculation-logic-in-an-entity-or-in-the-business-layer/129307

в приведённом примере я увидел , по крайней мере, 3 аггрегата: товары, скидки, ценоборазование, ну или можно сделать два: товары + скидки/цены
Re[17]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 12.02.20 04:48
Оценка:
Здравствуйте, takTak, Вы писали:
T>ну а немного погуглить-то тоже времени нет?

T>вот: https://hackernoon.com/making-a-case-for-domain-modeling-17cf47030732

T>https://softwareengineering.stackexchange.com/questions/129305/should-i-put-calculation-logic-in-an-entity-or-in-the-business-layer/129307
Ну, вот это как раз примеры из области "обнять и плакать". Вторая статья ни о чём — там нет конкретики.
А в первой видно, как малейшее изменение бизнес-правил приводит к неконтролируемому расползанию изменений в коде. Например: лимит овердрафта зависит от "типа" счёта (обычный/премиум). Ну, то есть исходный пример там нифига не anemic, т.к. логика определения политики овердрафта вшита внутрь класса "счёт". В анемике такого быть не должно — в худшем случае у ентити "account" будет атрибут overdraftLimit (вы заметили, что в исходном примере переменная limit берётся ниоткуда?). В более прямом — будет отдельный метод getOverdraftLimit(accountId) в интерфейсe MoneyTransferService, который уже можно перегружать в зависимости от наших потребностей.
Так вот, в "DDD" варианте у автора политика овердрафта является полем счёта. С одной стороны, это хорошо — мы можем иметь несколько вариантов политики овердрафта на всю систему, и все счета будут ссылаться на один из вариантов. И мы можем одной транзакцией увеличить или уменьшить значения лимитов для целой группы счетов.
С другой стороны, у нас единственный способ отличить дебитовый счёт от кредитного — это поднять в память счёт, и его policy.
Политика овердрафта пришивается к счёту при его создании, и потом её изменение потребует update в базу.
Попробуйте реализовать в этом сервисе правило типа "лимит овердрафта по кредитному счёту клиента определяется статусом клиента".
Ой. Надо делать mass update в таблицу счетов.
Попробуйте реализовать в этом сервисе правило типа "лимит овердрафта по кредитному счёту клиента равен половине суммы балансов всех его счетов".
Ой, теперь вообще всё посыпалось — policy надо конструировать на лету, причём непонятно как. Ей придётся бегать в MoneyTransactionService, чтобы запросить все счета клиента — имеем кольцевую зависимость.
При этом ломать существующую систему нельзя — наверняка для каких-то клиентов (или отдельных счетов) у нас будут сохранены фиксированные лимиты.

В анемике все изменения, касающиеся политики овердрафта, спрятаны в метод getOverdraftLimit(). У него может быть расширен список параметров — например, мы будем передавать туда accountId, customerId, managerId.
И в первой реализации у нас будет простая логика, которая возвращает значение атрибута Account.overdraftLimit. Во второй — комплексная логика с запросом баланса клиента из базы.
В третьей — у нас будет что-то вроде:
IEnumerable<IOverdraftRule> overdraftRules = from accRule in AccountOverdraftRules where accRule.Match(accountId) select accRule;
overdraftRules = overdraftRules.Concat(from custRule in CustomerOverdraftRules where custRule.Match(customerId) select custRule);
overdraftRules = overdraftRules.Concat(from mgrRule in ManagerOverdraftOverrideRules where mgrRule.Match(managerId) select mgrRule);
return overdraftRules.OrderBy(r=>r.Priority).FirstOrDefault().Limit()

И всё это — не трогая иерархию entities и другие сервисы. В DDD придётся править и entities, и сервисы, и служебные классы. Нуинахрена? Не говоря уже о том, что производительность окончательного решения будет гарантированно хреновой.
T>в приведённом примере я увидел , по крайней мере, 3 аггрегата: товары, скидки, ценоборазование, ну или можно сделать два: товары + скидки/цены
Отлично. Давайте двигаться дальше: какие будут методы у этих агрегатов?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[18]: DDD для небольших проектов.
От: takTak  
Дата: 12.02.20 06:39
Оценка:
ты сделал замечательный обзор двух моих первых ссылок из гугла, но давай, пожалуйста,

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

во-вторых, касательно самого примера предлагаю обозначить мыслимые варианты моделирования одной проблемы, типа есть один сценарий: пользователь изменил скидку на товар ХХХ на ХХ процентов, после этого
1) продукт сам подсчитал себе цену, когда клиент его запросил
2) сервис подхватил продукт, клиента, вытянул скидку для него, потом он высчитал товарную группу, применил скидку для неё и т.д. и выдал цену (перед или после того, как клиент запросил цену)
3) сущность "скидка" отправила броадкастное сообщение, что она с такими-то параметрами изменилась, подписчики на это событие, а именно сервис или сущность "цена" обработала событие путём дополнительного изменения к уже имеющимся куммулятивным изменениям и установила конечную цену (перед или после того, как клиент запросил цену)
я что-то ещё забыл?
Re[19]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 12.02.20 08:26
Оценка:
Здравствуйте, takTak, Вы писали:

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


T>во-первых, вначале вернёмся на один шаг назад:

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

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

T>1) продукт сам подсчитал себе цену, когда клиент его запросил
T>2) сервис подхватил продукт, клиента, вытянул скидку для него, потом он высчитал товарную группу, применил скидку для неё и т.д. и выдал цену (перед или после того, как клиент запросил цену)
T>3) сущность "скидка" отправила броадкастное сообщение, что она с такими-то параметрами изменилась, подписчики на это событие, а именно сервис или сущность "цена" обработала событие путём дополнительного изменения к уже имеющимся куммулятивным изменениям и установила конечную цену (перед или после того, как клиент запросил цену)
T>я что-то ещё забыл?
1. Для начала хочется понять, что такое "пользователь изменил скидку на товар XXX" — это мы говорим в рамках требования №5 "иногда мы можем захотеть сделать скидку на конкретный заказ", или в рамках какого-то другого требования?
2. Не очень понятно, что такое "сущность скидка". Какие примерно будут атрибуты у этой сущности, чтобы я мог её сопоставить с доменной моделью?
3. Не очень понятно, что такое сущность "цена". Цена является частью конкретной позиции конкретного заказа. До заказа её не существует, после оформления заказа изменять её нельзя. Так что третий вариант я вообще не понимаю.

Перед тем, как выбирать между всеми мыслимыми вариантами моделирования, хотелось всё же понять структуру доменной модели. Описание в терминологии заказчика я уже изложил — в их мире существует 6 существительных (товар, группа, продавец, позиция прайс-листа, позиция заказа, заказ). Существует 5 "сценариев", или "требований", которые не выражены в терминах отдельных сущностей доменной модели, а сформулированы в рамках задачи "рассчитать отпускную цену для позиции заказа".
Нам нужно, в первую очередь, спроектировать информационную архитектуру — если нужны какие-то сущности, отсутствующие в оригинальной доменной модели (описанной непрофессионалом в IT), то их надо добавить.
Затем нужно добавить сценарии, которых не хватает для полного решения — очевидно, помимо сценария "рассчитать стоимость заказа" должны быть ещё какие-то сценарии, которые заказчик самостоятельно не формулирует.
Ну, и потом можно перейти уже к моделированию архитектуры — то ли это будет сервис, то ли набор агрегатов, то ли у нас где-то будет Command Log и Query, то ли ещё что.
Как решать задачу в терминах анемик архитектуры мне понятно от и до. Она же влияет (косвенным образом) на информационную архитектуру, и на UI сценарии, в которых мы эту архитектуру выставляем.

А в рамках данной дискуссии мне интересно, к какому решению мы придём через DDD.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[20]: DDD для небольших проектов.
От: takTak  
Дата: 12.02.20 09:23
Оценка:
у меня почему-то осталось в памяти, что основная проблема была со скидками, прочём все события были перечислены именно в том ключе, что типа сделали скидку на товарную группу, сделали скидку на товар, сделали скидку в зависимости от клиента или продавца, поэтому для меня это и было сигналом того, что надо создавать отдельный аггрегат "скидка", со сущностью же "скидка" внутри,

сценарий я выбрал для начала самый простой, варианты моделирования получились из твоего следующего ответа на мои две ссылки из гугла, причём я просто попытался очертить варианты в том ключе, то
1) есть rich domain model, где каждый объект сам решает все проблемы
вот тут тоже на эту тему, вроде: https://www.baeldung.com/ddd-double-dispatch
2) имеет место быть transaction script + anemic model, где какой-то сервис всё делает за всех
в применении к ддд, когда его не поняли, проскакивает и такое, как domain service
3) никакого центрального места, где всё за всех высчитывается, нет, каждая сущность делает только то, что ей по чину и рангу положено и не тянет на себя одеяло, как это имеет место быть при варианте 1)

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

кстати, вот тут есть какое-то похожее описание на твой случай:
https://www.researchgate.net/publication/221321492_Agile_enterprise_software_development_using_domain-driven_design_and_test_first
Re[21]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 12.02.20 10:35
Оценка:
Здравствуйте, takTak, Вы писали:

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

Зачем память, если все ходы записаны
Автор: Sinclair
Дата: 10.02.20
.
В задаче фигурируют не скидки, а наценки.
То есть отпускная цена позиции формируется по формуле Cost*(1+Markup).
Скидка, как таковая, появляется только в конце — это если менеджер оформляет заказ для продавца (реселлера), и ему "не нравится" окончательная цена, рассчитанная по правилам, то он хочет её корректировать.
Основное отличие от скидок — наценка бывает только одна на каждую позицию. Нам не нужно комбинировать наценки друг с другом.
Если дефолтная наценка — 4%, а для данного продавца — 5%, то в результате цена будет равна Cost*(105%), а не Cost*1.05*1.04.

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

T>1) есть rich domain model, где каждый объект сам решает все проблемы
T> вот тут тоже на эту тему, вроде: https://www.baeldung.com/ddd-double-dispatch
T>2) имеет место быть transaction script + anemic model, где какой-то сервис всё делает за всех
T> в применении к ддд, когда его не поняли, проскакивает и такое, как domain service
Я правильно понимаю, что https://hackernoon.com/making-a-case-for-domain-modeling-17cf47030732 это как раз "ддд, когда его не поняли"? Там же в DDD фигурирует domain service.
T>3) никакого центрального места, где всё за всех высчитывается, нет, каждая сущность делает только то, что ей по чину и рангу положено и не тянет на себя одеяло, как это имеет место быть при варианте 1)

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

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

По простому: есть каталог на 10000 наименований; есть 10000 покупателей. Потенциально мы говорим о 10^8 возможных ценах, которые могут появиться в заказе.
Но у нас нет никаких ста миллионов "сущностей цена", которые могли бы подписаться на оповещения вроде "изменилась скидка для пользоватееля xxx" или "изменилась себестоимость для товара yyy" и отреагировать на них.
Такая штука была бы нужна в случае наличия материализованного "прайс листа" со 10000 колонок — по одной на пользователя.

T>кстати, вот тут есть какое-то похожее описание на твой случай:

T>https://www.researchgate.net/publication/221321492_Agile_enterprise_software_development_using_domain-driven_design_and_test_first
Ну да, похоже. Правда, там объём проекта в сотни раз больше. Но что-то похожее есть.
Я так до конца и не понял, как работает их система. Как связаны PricingConcept c PriceFormulaElement неясно. Кто там на ком стоял, кто из них entity, а кто сервис — хз. Понятно, вроде бы, что у них там есть агрегат типа Deal, который сам себе цену вычисляет. Непонятно, применимо ли это в нашем случае — слишком много кода вырезано, а то, что осталось, не является валидным.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: DDD для небольших проектов.
От: Буравчик Россия  
Дата: 12.02.20 11:51
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Вот мне интересно, какие объекты будут появляться в DDD, и как они будут работать. Будет ли rich object model, или анемика?


А в анемике как будет?

P.S. Мне просто интересно сравнить, как бы я спроектировал
Best regards, Буравчик
Re[2]: DDD для небольших проектов.
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 12.02.20 12:46
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>>нормально будет? Или он только хорош, когда у нас есть n (например, n=100) сущностей?

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

S>Вкратце: в домене (по описаниям отдела продаж) есть

S>- товары
S>- группы товаров (товар входит в 1, 2 или 3 группы)
S>- реселлеры (продавцы)
S>- позиции прайслиста. Каждая позиция сопоставляет "базовую цену" одному товару.
S>- заказы. Заказ — как обычно: продавец, покупатель, 1..N позиций: (товар, количество, отпускная цена).

...
  Смотришь на это и берет тоска


-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re[22]: DDD для небольших проектов.
От: takTak  
Дата: 12.02.20 19:14
Оценка:
T>>2) имеет место быть transaction script + anemic model, где какой-то сервис всё делает за всех
T>> в применении к ддд, когда его не поняли, проскакивает и такое, как domain service
S>Я правильно понимаю, что https://hackernoon.com/making-a-case-for-domain-modeling-17cf47030732 это как раз "ддд, когда его не поняли"? Там же в DDD фигурирует domain service.

просто всегда велик соблазн сделать по привычке, поэтому народ вместо того, чтобы делать rich domain model по-кошерному, пишет процедуру, которая всё за всех делает, называя её, однако, domain service

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

получается, что наш "заказ" содержит в себе проекции этих перечисленных сущностей с необходимыми нам атрибутами,
теперь по поводу реализации: мне лично нравится идея с формулировкой правил подсчёта цены в виде fluent api, что то типа такого https://github.com/NRules/NRules/wiki/Getting-Started, https://stackoverflow.com/questions/42529694/how-to-implement-specification-pattern
т.е. "заказ" после инициализации может отсканировать имеющиеся в системе спецификации и применить к себе подходящие, на выходе получаем цену, как такая идея?

да, динамики нет, если вдруг нужно формулировать новое правило для надценки, то придётся ручками писать новую спецификацию,

я не совсем понял роль того, кто может применить к заказу произвольную скидку, это тоже надо как-то учитывать, но по идее, будет какое-то пользовательское действие\событие, на него надо будет реагировать
Re[23]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.02.20 03:55
Оценка:
Здравствуйте, takTak, Вы писали:


T>ок, обратно к нашим баранам,

T>получается, что для калькуляции цен на лету нужна совокупная информация: товарная группа, сам товар (если наценка -скидка только на него), покупатель (с каким-то статусом или без оного), продавец, вот вроде и всё,
Ну, вроде бы да.

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

T>теперь по поводу реализации: мне лично нравится идея с формулировкой правил подсчёта цены в виде fluent api, что то типа такого https://github.com/NRules/NRules/wiki/Getting-Started, https://stackoverflow.com/questions/42529694/how-to-implement-specification-pattern
T>т.е. "заказ" после инициализации может отсканировать имеющиеся в системе спецификации и применить к себе подходящие, на выходе получаем цену, как такая идея?
В целом неплохо. Но интересны детали. Пример этого fluent api крайне далёк от моего понимания — либо там очень хитрая инверсная семантика, либо он нежизнеспособен.
Типа запустили правило, оно там что-то просканировало, что-то изменило, кого-то оповестило. Кастомер давно свой заказ оплатил и получил — зачем ему эта нотификация?
Наверное, там просто пример и описание решительно невнятное. Потому что сходу неясно, почему нельзя было просто написать всё то же самое на linq2objects и не изобретать горы велосипедов.
var ordersToDiscount = from c in Customers 
  from o in Orders
  where c.IsPreferred && o.Customer = c && o.IsOpen() && !o.IsDiscounted()
  select o;

  ordersToDiscount.Set(o=>o.PercentDiscount, 10.0);

Если я правильно понял, то у нас будет какой-то метод у класса Order, который наш агрегат. Можете набросать примерную сигнатуру и устройство этого метода?


T>да, динамики нет, если вдруг нужно формулировать новое правило для надценки, то придётся ручками писать новую спецификацию

Ну, совсем без ручной работы не получится — вопрос в изоляции изменений.

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


Вот то-то и оно, что DDD провоцирует думать в неверном направлении. Какие события? Какие действия?
"Действий" у нас, по большому счёту, два с половиной:
1. Рассчитать предполагаемую отпускную цену для каждой позиции. (Показываем проект заказа в UI)
2. Сохранить заказ с заданными отпускными ценами
И ещё пол-сценария: если пользователь имеет уровень менеджера, то мы предлагаем ему возможность скорректировать цены между п.1 и п.2 вручную. Всё.

Никаких "событий", никаких "циклов в поисках кастомеров". Все эти циклы и прочие fluent прекрасны тогда, когда у нас есть система уравнений, и мы её решаем в удобном для нас порядке. В реальной системе основные события порождаются пользователями, ну и плюс асинхронными системами. Вот эта вот идея про "запихать в доменную модель всех покупателей и их заказы и запустить все правила" — как она бы реализовалась, скажем, у Амазона?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[24]: DDD для небольших проектов.
От: takTak  
Дата: 13.02.20 06:49
Оценка:
этот "заказ" в приведённом разрезе, наверное, должен обладать методами "подсчитай цену" и "сохрани", сигнатура void, без аргументов, так как вроде всё, что нам надо для подсчётов, уже в агрегате есть, другой метод "скорректируй цену", реагирует на событие "менеджер изменил цену", аргументы тут те, которые нужны: цена или процент(непонятно что он набивает), может, userId- если это как-то протоколируется

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

идея с fluent api мне нравится тем, что эти спецификации можно легко прилепить к имеющемся в цепочку; ты же в примере привёл лишь использование только одной, как ты будешь формулировать, если у тебя 3, 5, 7 формул подсчёта надценки? с if else? кроме того, сама реализация должна быть где-то в репозитарии или ты будешь DbContext тащить прямо в агрегат?

у амазона это раньше было сделано так, что для товаров, цен были свои отдельные предметные области, продукт тащился своим микросервисом, цена- своим микросервисом, композиция происходила на уровне UI (composite ui), т.е. могло теоретически быть и так, что , например, продукт сразу прогрузился, а цена- чуть позднее, ну или наоборот
Отредактировано 13.02.2020 6:51 takTak . Предыдущая версия .
Re[25]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.02.20 07:03
Оценка:
Здравствуйте, takTak, Вы писали:

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

Спасибо.
Пока что всё плохо.
1. У нас в классе "заказ" есть уже две обязанности — "сохрани" и "подсчитай цену". Уже плохо. То есть тащим как бизнес-логику, так и DbContext.
2. Опять непонятно, что за событие такое "менеджер изменил цену", и почему есть какой-то отдельный метод "скорректируй цену". Для чего это нужно?
3. Непонятно, как работает этот агрегат с позициями заказа.

T>внутри "подсчитай цену" будет либо отсылка к какой-то rule engine, либо, как у тебя в примере, какой-то собственный алгоритм калькуляции, вызываться этот метод будет после событий "покупатель добавил товарную позицию" или "покупатель изменил количество"

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

T>идея с fluent api мне нравится тем, что эти спецификации можно легко прилепить к имеющемся в цепочку; ты же в примере привёл лишь использование только одной, как ты будешь формулировать, если у тебя 3, 5, 7 формул подсчёта надценки? с if else? кроме того, сама реализация должна быть где-то в репозитарии или ты будешь DbContext тащить прямо в агрегат?

Я вообще против агрегатов и rich модели — она даже на hello-world примерах сосёт как пылесос.
Прелесть анемиков как раз в том, что нет мучений вроде обратных зависимостей агрегатов от DbContext. Все entity — POCO, все сервисы — stateless.

T>у амазона это раньше было сделано так, что для товаров, цен были свои отдельные предметные области, продукт тащился своим микросервисом, цена- своим микросервисом, композиция происходила на уровне UI (composite ui), т.е. могло теоретически быть и так, что , например, продукт сразу прогрузился, а цена- чуть позднее, ну или наоборот

Я вам к тому, что "цена" в Амазоне — сущность эфемерная. Изменение тарифа где-то в одном углу системы не вызывает каскад миллиардов событий "скорректировать цену", как в Excel.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[26]: DDD для небольших проектов.
От: takTak  
Дата: 13.02.20 08:06
Оценка:
T>>этот "заказ" в приведённом разрезе, наверное, должен обладать методами "подсчитай цену" и "сохрани", сигнатура void, без аргументов, так как вроде всё, что нам надо для подсчётов, уже в агрегате есть, другой метод "скорректируй цену", реагирует на событие "менеджер изменил цену", аргументы тут те, которые нужны: цена или процент(непонятно что он набивает), может, userId- если это как-то протоколируется
S>Спасибо.
S>Пока что всё плохо.
S>1. У нас в классе "заказ" есть уже две обязанности — "сохрани" и "подсчитай цену". Уже плохо. То есть тащим как бизнес-логику, так и DbContext.
S>2. Опять непонятно, что за событие такое "менеджер изменил цену", и почему есть какой-то отдельный метод "скорректируй цену". Для чего это нужно?
S>3. Непонятно, как работает этот агрегат с позициями заказа.

пока я ещё вроде ничего про реализацию"сохрани" не говорил: мы что используем: как ты когда-то намекал на Log / Query, т.е. Write & ReadModel, как понимаю, или обычную персистенцию в базу данных ? внутренне будет установлен флажок, что ни изменение цены (в том числе и менеджером), ни добавление или удаление позиций недопустимо, в принципе, может, стоит метод переименовать в "оформить заказ"

ты сам вроде упомянул, что если пользователь- менеджер, то он может изменить цену или я что-то не так понял?

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

T>>внутри "подсчитай цену" будет либо отсылка к какой-то rule engine, либо, как у тебя в примере, какой-то собственный алгоритм калькуляции, вызываться этот метод будет после событий "покупатель добавил товарную позицию" или "покупатель изменил количество"

S>Тут, по-видимому, неявно предполагается, что заказ у нас существует в состоянии "проект", когда его можно менять, и в состоянии "отправлен", когда фиксируются количества и цены.
S>По мне так это плохая идея. Потому, что пересчитывать цены надо не только тогда, когда пользователь что-то добавил или изменил в заказе, но и при изменении списка правил. Кто какие события будет порождать, и кто на какие события будет подписываться? Мне проще вообще не иметь заказов в "предварительном" состоянии.

т.е. цена может быть изменена задним числом? чего-то я этого не понимаю, это как?

S>Тем не менее — давайте всё же посмотрим на тело "подсчитай цену" в агрегате "заказ". Все необходимые требования в топике приведены.

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

T>>идея с fluent api мне нравится тем, что эти спецификации можно легко прилепить к имеющемся в цепочку; ты же в примере привёл лишь использование только одной, как ты будешь формулировать, если у тебя 3, 5, 7 формул подсчёта надценки? с if else? кроме того, сама реализация должна быть где-то в репозитарии или ты будешь DbContext тащить прямо в агрегат?

S>Я вообще против агрегатов и rich модели — она даже на hello-world примерах сосёт как пылесос.
S>Прелесть анемиков как раз в том, что нет мучений вроде обратных зависимостей агрегатов от DbContext. Все entity — POCO, все сервисы — stateless.

если мы ограничим агрегат только тем, что ему нужно: внутренним состоянием, никакого DbContext ему не нужно

T>>у амазона это раньше было сделано так, что для товаров, цен были свои отдельные предметные области, продукт тащился своим микросервисом, цена- своим микросервисом, композиция происходила на уровне UI (composite ui), т.е. могло теоретически быть и так, что , например, продукт сразу прогрузился, а цена- чуть позднее, ну или наоборот

S>Я вам к тому, что "цена" в Амазоне — сущность эфемерная. Изменение тарифа где-то в одном углу системы не вызывает каскад миллиардов событий "скорректировать цену", как в Excel.

это я тоже не понял, что значит: "не вызывает каскадных изменений" ? если сегодня скидка на трусы на 20 %, а завтра — на всё бытовую технику, а послезавтра — на пылесосы фирмы ххх, то что значит: " без каскадных изменений"
Re[27]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.02.20 09:52
Оценка:
Здравствуйте, takTak, Вы писали:

T>пока я ещё вроде ничего про реализацию"сохрани" не говорил: мы что используем: как ты когда-то намекал на Log / Query, т.е. Write & ReadModel, как понимаю, или обычную персистенцию в базу данных ?

Я потерял нить. Вот цитата из вашего ответа:

тот "заказ" в приведённом разрезе, наверное, должен обладать методами "подсчитай цену" и "сохрани"


T>внутренне будет установлен флажок, что ни изменение цены (в том числе и менеджером), ни добавление или удаление позиций недопустимо, в принципе, может, стоит метод переименовать в "оформить заказ"

T>ты сам вроде упомянул, что если пользователь- менеджер, то он может изменить цену или я что-то не так понял?
Он может изменить цену при создании заказа. В анемике мы имеем, грубо говоря, два метода:
public OrderId PlaceOrder(userId, resellerId, List<(string sku_id, int qty)> items);
public OrderId PlaceOrder(userId, resellerId, List<(string sku_id, int qty, Decimal price)> items);

Второй доступен только менеджерам.
Где-то невдалеке мы имеем метод
Decimal GetPrice(customerId, resellerId, sku_id);

Он не является частью заказа, т.к. наш UI, естественно, умеет показывать пользователю каталог товаров с ценами на них. На самом деле у нас даже будет метод
List<(string sku_id, decimal price))> GetPrices(resellerId);

Который сразу возвращает весь прайс-лист для конкретного реселлера.

Но это в анемике. А в DDD у нас где будет это всё?

S>>По мне так это плохая идея. Потому, что пересчитывать цены надо не только тогда, когда пользователь что-то добавил или изменил в заказе, но и при изменении списка правил. Кто какие события будет порождать, и кто на какие события будет подписываться? Мне проще вообще не иметь заказов в "предварительном" состоянии.

T>т.е. цена может быть изменена задним числом? чего-то я этого не понимаю, это как?
Как раз не может. После PlaceOrder никакого изменения цены не может быть. До PlaceOrder не существует никакого Order. Если бы мы сохранили Order в состоянии "предварительный", то в нём нельзя фиксировать цену — датой заказа считается та, когда он "отправлен". Если мы подготовили его в январе, а потом пришёл новый прайслист, и мы отправили заказ уже в феврале, то оплачивать придётся по ценам февраля.

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

Вот этого я и не понимаю. Хочу увидеть пример кода. Потом можно будет попытаться понять, откуда агрегат будет брать это внутреннее состояние.
T>если мы ограничим агрегат только тем, что ему нужно: внутренним состоянием, никакого DbContext ему не нужно
Вот как раз тут важны детали.

T> это я тоже не понял, что значит: "не вызывает каскадных изменений" ? если сегодня скидка на трусы на 20 %, а завтра — на всё бытовую технику, а послезавтра — на пылесосы фирмы ххх, то что значит: " без каскадных изменений"

Это значит, что нет никаких "событий" и "подписок". Есть просто метод getPrice(goodId, customerId), который вызывается при загрузке страницы. Где-то у него внутри перебираются pricing Rules, которые матчат товар и кастомера; применяются всякие комбинации. Результат отправляется клиенту и навсегда забывается. Плюс-минус кэширование.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[28]: DDD для небольших проектов.
От: takTak  
Дата: 13.02.20 11:28
Оценка:
T>>пока я ещё вроде ничего про реализацию"сохрани" не говорил: мы что используем: как ты когда-то намекал на Log / Query, т.е. Write & ReadModel, как понимаю, или обычную персистенцию в базу данных ?
S>Я потерял нить. Вот цитата из вашего ответа:
S>

S>тот "заказ" в приведённом разрезе, наверное, должен обладать методами "подсчитай цену" и "сохрани"


ну, переименуй это "сохрани" в "создать заказ" или "оформить заказ" — не знаю, как там у вас что в UI называется


T>>т.е. цена может быть изменена задним числом? чего-то я этого не понимаю, это как?

S>Как раз не может. После PlaceOrder никакого изменения цены не может быть. До PlaceOrder не существует никакого Order. Если бы мы сохранили Order в состоянии "предварительный", то в нём нельзя фиксировать цену — датой заказа считается та, когда он "отправлен". Если мы подготовили его в январе, а потом пришёл новый прайслист, и мы отправили заказ уже в феврале, то оплачивать придётся по ценам февраля.

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

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

S>Вот этого я и не понимаю. Хочу увидеть пример кода. Потом можно будет попытаться понять, откуда агрегат будет брать это внутреннее состояние.
T>>если мы ограничим агрегат только тем, что ему нужно: внутренним состоянием, никакого DbContext ему не нужно
S>Вот как раз тут важны детали.
нужны ли для калькуляции какие-то вызовы в базу данных или я могу загрузить все интересующие меня аргументы при создании агрегата?

T>> это я тоже не понял, что значит: "не вызывает каскадных изменений" ? если сегодня скидка на трусы на 20 %, а завтра — на всё бытовую технику, а послезавтра — на пылесосы фирмы ххх, то что значит: " без каскадных изменений"

S>Это значит, что нет никаких "событий" и "подписок". Есть просто метод getPrice(goodId, customerId), который вызывается при загрузке страницы. Где-то у него внутри перебираются pricing Rules, которые матчат товар и кастомера; применяются всякие комбинации. Результат отправляется клиенту и навсегда забывается. Плюс-минус кэширование.
так когда происходит перебор правил, в системе может появиться изменение, типа сегодня все трусы снова на 5% дороже, ты хочешь сказать что амазон ждёт следующего дня, чтобы скомпилировать правила и перезапустить сервер?

и ещё: покупатель просто каталога всех товаров не видит, он всегда выбирает вначале продавца, и только потом- товары?
Re[28]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 13.02.20 11:41
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Но это в анемике. А в DDD у нас где будет это всё?


Соотв. application или domain service.
Кодом людям нужно помогать!
Re[26]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 13.02.20 11:44
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Прелесть анемиков как раз в том, что нет мучений вроде обратных зависимостей агрегатов от DbContext. Все entity — POCO, все сервисы — stateless.


А как аггрегат может зависеть от DbContext? Он зависит от других сущностей, которые вообще могут быть DTO, т.е. вообще не зависеть от DbContext.
Кодом людям нужно помогать!
Re[29]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.02.20 13:16
Оценка:
Здравствуйте, takTak, Вы писали:

T>ну, переименуй это "сохрани" в "создать заказ" или "оформить заказ" — не знаю, как там у вас что в UI называется

Ок, переименовал. В любом случае, тот "оформить заказ", который я себе представляю, требует наличия dbContext.
Как без него обойдётся агрегат в DDD —

T>если создание заказа- это размещение заказа, т.е. placeOrder, то что тогда такое "отправить заказ" ? Вот это я вообще не понимаю: "Если мы подготовили его в январе, а потом пришёл новый прайслист, и мы отправили заказ уже в феврале", кто его подготовил?

Ну, так это я пытаюсь догадаться, как работает предлагаемая вами архитектура. Из того, что есть какие-то события типа "пользователь изменил цену", или методы "добавить позицию", я понимаю, что агрегат "заказ" у вас обретает существование ещё до того, как будет "сохранён", или "отправлен" — в общем, до того, как начнётся собственно выполнение заказа.
В обычных интернет-магазинах такие штуки называются чем-то типа "cart" — типа я интерактивно складываю и вынимаю товары из корзинки; а потом, когда я уже готов на выход, корзинка превращается в заказ при помощи операции checkout.
Я не хотел усложнять задачу, т.к. у нас тогда появляется ещё два агрегата (корзина и "элемент корзины"), и мы рискуем вообще не закончить
Поэтому условимся считать, что никакой корзинки нету, и заказ, как таковой, возникает только в момент нажатия на кнопку "заказать". А аналог корзинки у нас эфемерен, существует только в воображении клиента, и нас не интересует.
Ну, типа там какой-то джаваскрипт, интересоваться которым — не барское дело. Наше дело — выставить API для веб-клиента; и с точки зрения этого API до момента "заказать" заказа не существует, а после этого момента изменить уже ничего нельзя.

T>нужны ли для калькуляции какие-то вызовы в базу данных или я могу загрузить все интересующие меня аргументы при создании агрегата?

Ну, это же вы архитектор. Я играю роль заказчика; диктовать вам, когда лазить в базу, я в этой роли не готов. Бизнес-требования я вам задал; если они непонятны — спрашивайте.
А как технический специалист я как раз и хочу понять, что такое этот DDD, и насколько ужасные получаюстя решения при его применении.

T>так когда происходит перебор правил, в системе может появиться изменение, типа сегодня все трусы снова на 5% дороже, ты хочешь сказать что амазон ждёт следующего дня, чтобы скомпилировать правила и перезапустить сервер?

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

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

Да, для упрощения понимания можно предположить, что у каждого продавца — свой "магазин". www.resellerA.com, www.resellerB.com.
Покупатель заходит в какой-то из этих магазинов, и видит ту версию каталога, которую мы отдаём соответствующему магазину (продавцу, reseller в родной номенклатуре).
С точки зрения покупателя не существует такой штуки, как "каталог всех товаров", есть только "каталог товаров у продавца resellerA".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[27]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.02.20 13:18
Оценка:
Здравствуйте, Sharov, Вы писали:

S>А как аггрегат может зависеть от DbContext? Он зависит от других сущностей, которые вообще могут быть DTO, т.е. вообще не зависеть от DbContext.

Вот я и пытаюсь понять, как агрегат отвяжется от DbContext, когда ему пытаются приделать методы, выполнение которых требует бегать в базу.
Пока что прояснения не наступает. Те примеры, которые приводили в этом топике — это обнять и плакать, типа lazy load, не к ночи будь помянут.
Либо просто авторы тактично уходят от вопроса "откуда в account возьмётся его overdraftPolicy". Ну, она типа просто есть — и всё.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[28]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 13.02.20 13:28
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>>А как аггрегат может зависеть от DbContext? Он зависит от других сущностей, которые вообще могут быть DTO, т.е. вообще не зависеть от DbContext.

S>Вот я и пытаюсь понять, как агрегат отвяжется от DbContext, когда ему пытаются приделать методы, выполнение которых требует бегать в базу.

Через репозиторий, вестимо.
Кодом людям нужно помогать!
Re[29]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.02.20 15:27
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Через репозиторий, вестимо.

Омг. А зачем нам репозиторий в дополнение к DbContext?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[30]: DDD для небольших проектов.
От: Sharov Россия  
Дата: 13.02.20 15:28
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>>Через репозиторий, вестимо.

S>Омг. А зачем нам репозиторий в дополнение к DbContext?

Чтобы отвязаться от DbContext напрямую.
Кодом людям нужно помогать!
Re[31]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.02.20 16:53
Оценка: +1
Здравствуйте, Sharov, Вы писали:

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

А, ну так-то да. Как бы ещё побольше кода написать, ага. Про эффективность комментировать не буду, пока не увижу код
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[31]: DDD для небольших проектов.
От: Mystic Artifact  
Дата: 13.02.20 17:25
Оценка:
Здравствуйте, Sharov, Вы писали:

S>>>Через репозиторий, вестимо.

S>>Омг. А зачем нам репозиторий в дополнение к DbContext?
S>Чтобы отвязаться от DbContext напрямую.
А это вообще зачем нужно? Самоцель? Я понимаю когда вводятся абстракции для чего-то. Но чаще (в личном опыте) — репозитории существуют сами ради себя, более того половина из них вообще не репозитории.
Re[30]: DDD для небольших проектов.
От: takTak  
Дата: 13.02.20 19:33
Оценка:
T>>ну, переименуй это "сохрани" в "создать заказ" или "оформить заказ" — не знаю, как там у вас что в UI называется
S>Ок, переименовал. В любом случае, тот "оформить заказ", который я себе представляю, требует наличия dbContext.
S>Как без него обойдётся агрегат в DDD —

если смотреть исторически, то агрегат в ддд вообще никак не связан с какой-то там базой данных, агрегат отвечает только за поведение и валидацию тех сущностей, которые находятся под его попечительством: допустим у тебя есть заказ, цена и условия которого могут измениться в будущем, но только при наличии каких-то условий, тогда пользователь, условно говоря, отправляет команду "изменить заказ с атрибутами: номер заказа , наименование операции "потребовать 25% скидки", пользовательский номер",тогда command handler переправляет агрегату событие с упомянутыми атрибутами, агрегат берёт номер заказа, загружает себя (вовсе необязательно из реляционной базы данных) из стримового потока, производит или не производит операцию, сохраняет себя в стримовом потоке, отправляет сообщение "я изменился, новая цена : хх" , и вот уже event handler, который подписан на это событие, вытаскивает аргументы и асинхронно сохраняет информацию в реляционной или ещё какой-то там базе данных, к которой обращается клиентская часть

но можно и посмотреть, до чего народ дошёл сейчас, без event sourcing, может чего и придумали, я лет 5 уже ничего подобного не делал

T>>если создание заказа- это размещение заказа, т.е. placeOrder, то что тогда такое "отправить заказ" ? Вот это я вообще не понимаю: "Если мы подготовили его в январе, а потом пришёл новый прайслист, и мы отправили заказ уже в феврале", кто его подготовил?

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

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

T>>нужны ли для калькуляции какие-то вызовы в базу данных или я могу загрузить все интересующие меня аргументы при создании агрегата?

S>Ну, это же вы архитектор. Я играю роль заказчика; диктовать вам, когда лазить в базу, я в этой роли не готов. Бизнес-требования я вам задал; если они непонятны — спрашивайте.
S>А как технический специалист я как раз и хочу понять, что такое этот DDD, и насколько ужасные получаюстя решения при его применении.

если делать по кошерному, я не имею права из агрегата тянуть какие-то вещи из левой для меня базы, все эти вещи либо должны быть мне доставлены либо должны быть частью моей предметной области, тогда я их могу либо у других агрегатов запросить, либо как-то скомпоновать с помощью какого-то domain service
Re[31]: DDD для небольших проектов.
От: takTak  
Дата: 13.02.20 20:26
Оценка:
ну вот тут народ на что-то похожее, что я пытался описать, ссылается, за 5 лет не так много изменилось

http://www.kamilgrzybek.com/design/simple-cqrs-implementation-with-raw-sql-and-ddd/

оставлю это здесь: вдруг время и желание будет позже посмотреть
Re[2]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 13.02.20 21:30
Оценка: 88 (2)
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Sharov, Вы писали:


S>>нормально будет? Или он только хорош, когда у нас есть n (например, n=100) сущностей?

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

DDD это просто описание инварианта системы и некоторые танцы с бубном, что бы этот инвариант гарантировать используя ООП. Работает на любом размера проекта, но не на любом размере надо использовать все что там есть. Если из него выкинуть ооп часть то все становиться сильно проще и логичнее.

S>Вот мне интересно, какие объекты будут появляться в DDD, и как они будут работать. Будет ли rich object model, или анемика?

Вот все перечисленные объекты и должны быть в модели. Буквально.
При этом модель должна быть непротиворечивой и все типы всегда валидны, следуя принципу make invalid state unrepresentable. Вот это и будет твоя модель — внутренний круг онион-архитектуры.
Потом добавляешь слой бизнес логики, который позволят трансформировать модель из одного состояния в другое. Типа как ордер превращается в валидный ордер, а валидный в оплаченный итп. Зачем оно так делает в данном случае несущественно, главное чтобы инвариант сохранялся.
В третьем слое — application logic, собственное сами сценарии, как и когда что работает. По-хорошему, это просто композиция функций из предыдущего слоя. И последний слой это инфраструктура или порты, который связывает все это с внешним миром. Там живут все DbConnection и прочие файлы. Зависимости всегда направлены внутрь. Собственно и все. Легко тестируется, легко поддерживать, легко понять что происходит, сломать трудно. Отлично масштабируется. Если калькулятор, то это все что надо, если кровавый энтерпрайз, то это один из BC. Как-то так.
Re[3]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.02.20 03:47
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

S>>Вот мне интересно, какие объекты будут появляться в DDD, и как они будут работать. Будет ли rich object model, или анемика?

PJ>Вот все перечисленные объекты и должны быть в модели. Буквально.
Это как раз понятно. Непонятно, какие будут методы у этих объектов.
PJ>При этом модель должна быть непротиворечивой и все типы всегда валидны, следуя принципу make invalid state unrepresentable. Вот это и будет твоя модель — внутренний круг онион-архитектуры.
Утопический принцип. Валидность состояния — это миф. Она имеет место только в рамках конкретного сценария. Вот, скажем, заказ в состоянии "новый" вполне может содержать в себе позиции, которых нет на складе. Ну, нету и нету.
А вот, например, зарезервировать заказ можно только в том случае, если всё заказанное готово к резерву.
Опять же — в состоянии "новый" заказ может не иметь данных о покупателе, но для того, чтобы сделать его "оплаченным" эти данные необходимы.
Чтобы перевести его в статус "в доставке" нам нужно иметь в заказе не просто адрес доставки, а адрес, распознанный нашим шиппинг-партнёром.
Как вы собираетесь описывать все эти ограничения при помощи системы типов?
Либо у нас будет 2^N типов "заказ", и трансформации будут менять тип заказа. Либо понятие "invalid" у нас сузится до несуществующего, и тип "заказ" будет разрешать вообще всё, убрав все статические ограничения. Кроме тривиальщины — ну там, типа "заказ не может содержать 0 позиций". И то есть риск, что придётся убрать и это ограничение под давлением обстоятельств.

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

То есть всё же имеем 2^N типов?
PJ>В третьем слое — application logic, собственное сами сценарии, как и когда что работает. По-хорошему, это просто композиция функций из предыдущего слоя. И последний слой это инфраструктура или порты, который связывает все это с внешним миром. Там живут все DbConnection и прочие файлы. Зависимости всегда направлены внутрь. Собственно и все. Легко тестируется, легко поддерживать, легко понять что происходит, сломать трудно. Отлично масштабируется. Если калькулятор, то это все что надо, если кровавый энтерпрайз, то это один из BC. Как-то так.
Отлично. Давайте применим эти абстрактные рассуждения к рассматриваемой задаче. Какие классы (или функции) у нас будут в "модели", какие — в "бизнес логике", какие — в "application logic".
Дьявол в деталях. Направление зависимостей, loose coupling и tight cohesion упоминают поклонники всех типов дизайна.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[31]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.02.20 03:51
Оценка:
Здравствуйте, takTak, Вы писали:

T>если смотреть исторически, то агрегат в ддд вообще никак не связан с какой-то там базой данных, агрегат отвечает только за поведение и валидацию тех сущностей, которые находятся под его попечительством: допустим у тебя есть заказ, цена и условия которого могут измениться в будущем, но только при наличии каких-то условий, тогда пользователь, условно говоря, отправляет команду "изменить заказ с атрибутами: номер заказа , наименование операции "потребовать 25% скидки", пользовательский номер",тогда command handler переправляет агрегату событие с упомянутыми атрибутами, агрегат берёт номер заказа, загружает себя (вовсе необязательно из реляционной базы данных) из стримового потока, производит или не производит операцию, сохраняет себя в стримовом потоке, отправляет сообщение "я изменился, новая цена : хх" , и вот уже event handler, который подписан на это событие, вытаскивает аргументы и асинхронно сохраняет информацию в реляционной или ещё какой-то там базе данных, к которой обращается клиентская часть

Это предполагает наличие изменяемой сущности "заказ". Как мы уже неоднократно обсудили в этом топике, в рамках нашей задачи такой штуки нет.

T>а вот не надо упрощать и чего-то придумывать, тогда мы вообще никогда не разберёмся, есть "карта покупок", ну так пусть и будет,

T>теперь опять к нашим баранам: когда пользователь залогинился и увидел список товаров, то он увидел их с ценой со скидками и надбавками или эти цены появляются только тогда, когда покупатель добавил их к "карзине для покупок" ?
Пользователь сразу видит список товаров с их ценами. Скидки/надбавки ему не видны — это закрытые подробности. Всё, что он видит — конкретную сумму.
T>>>нужны ли для калькуляции какие-то вызовы в базу данных или я могу загрузить все интересующие меня аргументы при создании агрегата?
T>если делать по кошерному, я не имею права из агрегата тянуть какие-то вещи из левой для меня базы, все эти вещи либо должны быть мне доставлены либо должны быть частью моей предметной области, тогда я их могу либо у других агрегатов запросить, либо как-то скомпоновать с помощью какого-то domain service
Ну, вот делайте по кошерному. Математику того, как формируется цена, я объяснил. Вот и расскажите, кто, откуда, и что будет тянуть, и как он это будет считать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 14.02.20 06:41
Оценка:
Здравствуйте, Sinclair, Вы писали:


S>Это как раз понятно. Непонятно, какие будут методы у этих объектов.

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

PJ>>При этом модель должна быть непротиворечивой и все типы всегда валидны, следуя принципу make invalid state unrepresentable. Вот это и будет твоя модель — внутренний круг онион-архитектуры.

S>Утопический принцип. Валидность состояния — это миф.
Нет, это не миф и пракрасно работает.

S>Она имеет место только в рамках конкретного сценария. Вот, скажем, заказ в состоянии "новый" вполне может содержать в себе позиции, которых нет на складе. Ну, нету и нету.

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

S>Опять же — в состоянии "новый" заказ может не иметь данных о покупателе, но для того, чтобы сделать его "оплаченным" эти данные необходимы.

S>Чтобы перевести его в статус "в доставке" нам нужно иметь в заказе не просто адрес доставки, а адрес, распознанный нашим шиппинг-партнёром.
S>Как вы собираетесь описывать все эти ограничения при помощи системы типов?
Вот так и буду. Я не понимаю где ты видишь проблему? NewOrder, ValidatedOrded, PaidOrder и функции NewOrder -> <ValidatedOrded, Error>, ValidatedOrded -> <PaidOrder, Error>

S>Либо у нас будет 2^N типов "заказ", и трансформации будут менять тип заказа.

Именно так.

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

S>То есть всё же имеем 2^N типов?
Я не знаю откуда взялась эта формула, но ты пишешь ее так как будь-то это много или плохо. Много типов это хорошо.


S>Отлично. Давайте применим эти абстрактные рассуждения к рассматриваемой задаче. Какие классы (или функции) у нас будут в "модели", какие — в "бизнес логике", какие — в "application logic".

В модели те, которые сохраняют инвариант модели. В BL которые обеспечивают логику модели, т.е. ValidateOrder : (NewOrder -> <ValidatedOrded, Error>), в AL use cases: AddArticle : ((Atricle, NewOrder) -> NewOrder), Checkout, etc
Re[5]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.02.20 07:06
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

PJ>Нет, это не миф и пракрасно работает.

Хотелось бы более убедительных доказательств.

PJ>Вот так и буду. Я не понимаю где ты видишь проблему? NewOrder, ValidatedOrded, PaidOrder и функции NewOrder -> <ValidatedOrded, Error>, ValidatedOrded -> <PaidOrder, Error>



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

S>>То есть всё же имеем 2^N типов?
PJ>Я не знаю откуда взялась эта формула, но ты пишешь ее так как будь-то это много или плохо. Много типов это хорошо.
Как откуда? Тип "ValidatedOrder" — это миф, т.к. нет концепции валидности, отдельной от конкретных сценариев.
При помощи типов мы можем описать лишь наличие либо отсутствие каких-то атрибутов.
Ну, то есть у нас есть
— наличие позиций в заказе
— наличие позиций заказа на складе
— готовность позиций заказа к отгрузке ("скомплектованность")
— наличие payment info в заказе
— наличие проведённой оплаты в заказе
— наличие адреса доставки в заказе
— наличие валидного адреса доставки в заказе
— состояние заказа "в доставке"
— состояние заказа "доставлен"
Вот у нас девять бинарных признаков. В патологическом случае это даёт 2^9 возможных комбинаций, для каждой из которых потенциально нужен свой тип. Итого — 512 типов, от "пустой заказ" до "заказ оплачен и доставлен".
Понятно, что некоторые комбинации в природе не встречаются — мы можем свернуть это пространство во что-то более компактное.
Ну, например, заметив, что не бывает такого, чтобы заказ уехал в доставку, минуя фазу зарезервированности, мы можем превратить 9 бинарных признаков в 4 признака с бОльшим количеством значений.
Тем не менее, у нас по-прежнему количество типов растёт экспоненциально. И операций типа "отгрузить" у нас получается не одна, потому что отгрузка ReadyOrder переводит его в ShippedOrder, а отгрузка PaidReadyOrder переводит его в PaidShippedOrder.
В какой системе типов мы можем это отразить? На всякий случай я рекомендую к прочтению серию статей Эрика Липперта "о колдунах и воинах", https://ericlippert.com/2015/05/11/wizards-and-warriors-part-five/.
Там подробно изложена моя точка зрения на попытки зафиксировать динамические ограничения в статической системе типов.
PJ>В модели те, которые сохраняют инвариант модели. В BL которые обеспечивают логику модели, т.е. ValidateOrder : (NewOrder -> <ValidatedOrded, Error>), в AL use cases: AddArticle : ((Atricle, NewOrder) -> NewOrder), Checkout, etc
Давайте конкретно. Задача есть? Есть. Перейдём от абстрактных рассуждений про валидность ордеров, и попробуем нарисовать иерархию классов для нашего простого случая с шестью сущностями, двумя сценариями, и четырьмя бизнес-правилами.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 14.02.20 07:48
Оценка: -1
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Poopy Joe, Вы писали:


PJ>>Нет, это не миф и пракрасно работает.

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

PJ>>Вот так и буду. Я не понимаю где ты видишь проблему? NewOrder, ValidatedOrded, PaidOrder и функции NewOrder -> <ValidatedOrded, Error>, ValidatedOrded -> <PaidOrder, Error>

S>

S>Как откуда? Тип "ValidatedOrder" — это миф, т.к. нет концепции валидности, отдельной от конкретных сценариев.

S>При помощи типов мы можем описать лишь наличие либо отсутствие каких-то атрибутов.
S>Ну, то есть у нас есть
S>- наличие позиций в заказе
S>- наличие позиций заказа на складе
S>- готовность позиций заказа к отгрузке ("скомплектованность")
S>- наличие payment info в заказе
S>- наличие проведённой оплаты в заказе
S>- наличие адреса доставки в заказе
S>- наличие валидного адреса доставки в заказе
S>- состояние заказа "в доставке"
S>- состояние заказа "доставлен"
S>Вот у нас девять бинарных признаков. В патологическом случае это даёт 2^9 возможных комбинаций, для каждой из которых потенциально нужен свой тип. Итого — 512 типов, от "пустой заказ" до "заказ оплачен и доставлен".
Че? Да, валидность, разумеется, зависит от твоих задач. Сферической валидности в вакууме не существует. Но причем тут бинарные признаки? Заказ валиден когда ты можешь выполнить. 9 там признаков или 109 совершенно несущественно. Два типа, ну три максимум.
NewOrder -> <ValidOrder, IncompleteOrder, Error>. IncompleteOrder -> <ValidOrder, IncompleteOrder, Error> Или тебе просто хочется доводить все до абсурда?

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

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

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

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

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

S>Давайте конкретно. Задача есть? Есть. Перейдём от абстрактных рассуждений про валидность ордеров, и попробуем нарисовать иерархию классов для нашего простого случая с шестью сущностями, двумя сценариями, и четырьмя бизнес-правилами.
Я изложил принципы. Попробуй описать свою систему используя их и уже будем обсуждать конкретику. Иначе, если ты все сходу отвергаешь, как невозможное, то конструктивно обсуждать нечего.
Re[4]: DDD для небольших проектов.
От: MadHuman Россия  
Дата: 14.02.20 08:12
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Poopy Joe, Вы писали:


PJ>>При этом модель должна быть непротиворечивой и все типы всегда валидны, следуя принципу make invalid state unrepresentable. Вот это и будет твоя модель — внутренний круг онион-архитектуры.

S>Утопический принцип. Валидность состояния — это миф. Она имеет место только в рамках конкретного сценария. Вот, скажем, заказ в состоянии "новый" вполне может содержать в себе позиции, которых нет на складе. Ну, нету и нету.
S>А вот, например, зарезервировать заказ можно только в том случае, если всё заказанное готово к резерву.
S>Опять же — в состоянии "новый" заказ может не иметь данных о покупателе, но для того, чтобы сделать его "оплаченным" эти данные необходимы.
S>Чтобы перевести его в статус "в доставке" нам нужно иметь в заказе не просто адрес доставки, а адрес, распознанный нашим шиппинг-партнёром.
S>Как вы собираетесь описывать все эти ограничения при помощи системы типов?
на самом деле есть возможности. посмотрите например (там на F# правда, на C# так изящно неполучится, точнее можно — но это уже будет не так удобно, и даже настолько неудобно что плюсы начнут теряться).
https://www.youtube.com/watch?v=Up7LcbGZFuo

или это
https://youtu.be/Bn132AtZLhc
Отредактировано 14.02.2020 8:18 MadHuman . Предыдущая версия .
Re[7]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 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. Непонятно, правда, как это ложится на изоляцию от базы.
То есть репозиторий (или кто у нас там отвечает за инициализацию агрегата) каким-то волшебным образом должен понять, что заказу для выполнения его работы надо подтащить список применимых к нему правил ценообразования, сделать это, а уж там-то заказ развернётся во всю мощь своей бизнес логики. По мне так это попахивает размазыванием логики по классам "заказ", "правило ценообразования", и "репозиторий заказов". Но может быть я неправильно понял эту идею?

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

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


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

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

Только вот весь основной DDD, который про rich object model — ровно противоположен этому подходу. Посмотрим, смогут ли его фанаты рассказать нам что-то интересное.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
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 Россия https://github.com/evilguest/
Дата: 14.02.20 09:18
Оценка:
Здравствуйте, takTak, Вы писали:
T>есть же, наверное, какая-то история заказов или сделок? кроме того, цену на "заказ" или "корзину покупок" вроде как менеджер тоже изменить может, т.е какая-то сущность этому соответствует?
История — конечно есть. Но её реализация делается примерно одинаково примерно везде.
По поводу цены — ещё раз расскажу всё то же самое: менеджер "меняет" цену прямо в UI. Вот он получил список товаров с рекомендованными ценами, отобрал некоторые из них, цену переопределил — отправил заказ.
Всё. До "отправки заказа" никакого заказа нет. Даже управление в наш код не попало ещё. Нет никаких событий, и никаких подписчиков у этих событий.
S>>Пользователь сразу видит список товаров с их ценами. Скидки/надбавки ему не видны — это закрытые подробности. Всё, что он видит — конкретную сумму.
T>ну раз так, то тогда попытки прилепить логику подсчёта цены к "заказу" были неправильными, "заказ" отвечает лишь за подсчёт конечной цены, ну ещё и эта коррекция цены менеджером тоже где-то должна произойти
Ну, вот ждём ответа от DDD — где же будет вся эта логика. И где будут храниться сами параметры ценообразования.

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

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

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

Ну, у нас пока размер задачи достаточно мелок, чтобы нарисовать всю "инфраструктуру" и всё "решение" с нуля в любом стиле. Пока что я совсем-совсем не понимаю ни первого ни второго пункта.
То есть — как именно устроена "подписка на события", кто их порождает, кто обрабатывает.
А если композиция логик — то как она выполняется. Если логика внутри агрегата — то какого агрегата? Если она вынесена — то куда? И не приведёт ли это к тому, что все изменения надо вносить в несколько мест — как в ту "вынесенную" логику, так и в каждый из "агрегатов", которые мы тут пытаемся идентифицировать.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
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 Россия https://github.com/evilguest/
Дата: 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 по потенциально безлимитному списку, а потом бегаем по этим мегабайтам алгоритмом Шлемиеля.
Нет, такой футбол нам не нужен.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
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 Россия https://github.com/evilguest/
Дата: 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 есть? Или нету?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
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 это агрегат и есть.
Re[13]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.02.20 16:10
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

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

Они были бы зависимыми, если бы мы писали на C++. Там бы мы параметризовали шаблонный тип Order всеми нужными нам аргументами, и безо всяких конструкторов писали бы прямо правила трансформации.
Там да — можно было бы описать, что рефанд реквест возможен только для ордера, у которого проведён платёж, независимо от его остальных компонентов состояния.
А в дотнете вы выписываете все эти типы ордеров руками.

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

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

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

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

S>>Может, я чего-то не понимаю, но у нас только что класс ShippedOrder развалился надвое, чтобы была возможность сделать два разных конструктора для DeliveredOrder.
А заодно придётся развалить пополам и класс ShippableOrder.
PJ>В каком, прости, месте? И в том, и в другом случае ты создаешь тип одын штука.
Как это в каком? Появились типы ShippedPaidOrder и ShippedUnpaidOrder. А как иначе мы выразим требование "заказ перед выдачей должен быть оплачен" при помощи типов?

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

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

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

Ну вот я и не понимаю, как добиться того, "чего я хотел". Ну сделаю я этот Tuple — дальше-то что?

PJ>Не надо. C# поддерживает nullable reference types.

Вы хотели сказать "non-nullable reference types"? Ну, этой фиче без году неделя, у меня пока нет опыта пользования. В описании авторов это выглядит так, что "ну, мы вроде иногда чего-то там проверяем", поэтому уверенности в железобетонности "скомпилировалось — значит работает" у меня нету.

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

PJ>Но так все это одинаково справедливо хоть с DDD, хоть без. C# это путь боли. Но это ж добровольный выбор.
Количество бойлерплейта радикально зависит от того, какой стиль кода выбран. Пока что у меня впечатление, что только лишь F# имеет (если имеет) достаточно мощную систему типов, чтобы описывать бизнес-правила с нужной точностью.
Потому что в шарпе мы быстро упираемся в то, что вместо сокращения объёма кода начинаем его увеличивать.
PJ>Следит. Хотя пока в виде варнинга, но варнинги можно и как ошибки поставить.
Надо будет поэкспериментировать.

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

Ок. А бизнес-логика, она в труъ DDD в агрегатах, или ещё где-то?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[14]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 14.02.20 16:52
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Poopy Joe, Вы писали:


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

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

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

S>А в дотнете вы выписываете все эти типы ордеров руками.
Вообще никакой разницы. Возможет только для PaidOrder

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

S>Ну, с доставкой не связана. И как нам это поможет?
Это поможет не мешать мух с котлетами, например.

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

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

S>>>Может, я чего-то не понимаю, но у нас только что класс ShippedOrder развалился надвое, чтобы была возможность сделать два разных конструктора для DeliveredOrder.
S>А заодно придётся развалить пополам и класс ShippableOrder.
PJ>>В каком, прости, месте? И в том, и в другом случае ты создаешь тип одын штука.
S>Как это в каком? Появились типы ShippedPaidOrder и ShippedUnpaidOrder. А как иначе мы выразим требование "заказ перед выдачей должен быть оплачен" при помощи типов?
А ты про них. Ну да, их стало два, ну в результате у тебя получился ровно один Delivered.



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

S>Отлично. Только что вы не верили, что у нас получится экспоненциальный рост количества классов. А теперь мы от фазы отрицания переходим к фазе гнева, да?
Может у нас разное понимание экспоненты? Ты это так любишь употреблять но порабы доказать уже математически.
Имеем,
                                                PaidOrder                       ShippedPaidOrder -------------------------
                                              /             \                /                                             \
NewOrder -> IncompleteOrder -> ValidatedOrder                 ShippableOrder                                                 Delivered  
                                              \ UnpaidOrder /                \  ShippedUnpaidOrder -> DeliveredWithPaiment /

Покажи мне тут экспоненту?
И, второй вопрос, как ты это обычно выражешь по-своему?

S>Как куда? В код бизнес-логики. Проверяем предусловия, и поехали. Пишется ровно такой же код "трансформации", только проверки предусловий выполняются прямо по месту действия правила, а не в момент конструирования аргумента.

А код он в C# не в типах живет? И как ты проверишь эту корретность когда поменяешь модель?

S>Ну вот я и не понимаю, как добиться того, "чего я хотел". Ну сделаю я этот Tuple — дальше-то что?

Ну откуда мне знать твою бизнес-задачу?! Я лишь заметил, что нет необходимости все валить в кучу, создавая God class с кучей методов.

PJ>>Не надо. C# поддерживает nullable reference types.

S>Вы хотели сказать "non-nullable reference types"? Ну, этой фиче без году неделя, у меня пока нет опыта пользования. В описании авторов это выглядит так, что "ну, мы вроде иногда чего-то там проверяем", поэтому уверенности в железобетонности "скомпилировалось — значит работает" у меня нету.
Фича называется nullable reference types, и, в описании авторов, работает как заявлено. https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references
Я не понял к чему этот комментарий? Я должен был как-то понять, что ты в нее не веришь и написать проверку на null?
До этого можно было использовать [NotNull] атрибут от JetBrains.

S>Количество бойлерплейта радикально зависит от того, какой стиль кода выбран.

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

S> Пока что у меня впечатление, что только лишь F# имеет (если имеет) достаточно мощную систему типов, чтобы описывать бизнес-правила с нужной точностью.

F# код моджно декомпилировать в C# код. За исключением некоторых фич компайлера, типы там обычные дотнетовские.
Несомненно на F# все это сильно проще, короче и лучше. Ну так и используй F#, если цель не страдать, а получить код который не надо отлаживать. Но если, по какой-то причине, надо использовать C#, то это не является шоустоппером.

S>Ок. А бизнес-логика, она в труъ DDD в агрегатах, или ещё где-то?

Задача агрегата поддерживать инвариант агрегата. Вот эта логика находится в нем. Бизнес-логика находится в другом слое, я выше описывал. Я выше ссылку на книжку давал, там это все подробно описано, советую найти и прочитать.
Отредактировано 14.02.2020 16:56 Poopy Joe . Предыдущая версия . Еще …
Отредактировано 14.02.2020 16:54 Poopy Joe . Предыдущая версия .
Re[37]: DDD для небольших проектов.
От: Mystic Artifact  
Дата: 14.02.20 22:50
Оценка: 82 (2)
Здравствуйте, Sharov, Вы писали:

S>Типа не по правилам -- https://raw.githubusercontent.com/aspnetboilerplate/aspnetboilerplate/master/doc/WebSite/images/abp-nlayer-architecture.png

S>Там между ORM и app. srv. есть domain layer.

Вводить генерик репозиторий — известный моветон, по уже озвученным причинам: не все энтити можно читать, изменять, удалять, получить все (в контексте какого пользователя, простите это вообще работает и что делать смежному коду?).

Я не пытаюсь переубедить, свою правду я давно узнал и другой уже (увы) не вижу. Но, на простой вопрос (с какой целью вводится эта абстракция) ты тоже внятного ответа не дал. Я имел некоторый (не очень большой) опыт с несколькими проектами организованными подобным образом, и всё что я понял — что вышло неуправляемое, неудобоваримое нечто, которое удовлетворяет той части системы, для которой она была написана изначально, но натянуть туда же фоновые задачи без костылей не выходило. Я подчеркну, тут дело не в DDD, а в существовании абстрактных репозиториев сущностей, которых нет: любая простецкая система с разнраничением прав — уже работает с их пересечением (простой фильтр), а пересечение в предметной области — бывает настолько замысловатым, что надо неделю разбираться, что бы объяснить постановщику задачи, что он не совсем прав. И если орг часть решаема, то тех часть в таких случаях решается тоже: мы просто работаем с БД, а с этой навороченной херней вообще не связываемся. Благо, на БД можно положится. Этих вопросов нет в первый год жизни проекта, но подобные вопросы всегда возникают когда проекту уже за 10 лет.
Я не хвалюсь, ни в коем случае, моя заслуга минимальна в них. Я видел 2 проекта овер 10+ лет. Первый вообще старичок, процедурный (скл). Второй более модный. Так вот, первый сильно выигрывает и до сих пор и работает. А второй — существует только как аморфное нечто, которое переписывают в третий раз, хотя сама по себе задача и поток данных там в десятки раз меньше и проще. Аморфное — потому что переписывается. Разумеется, он внедрен, но сам факт переписывания и то частичного намекает на крупное недовольствие.
Именно поэтому я старомоден в суждениях на этот счет. Линк кстати картинку не портит никак. А утыканные наугад абстракции — на мой взгляд портят.
Естественно, само по себе наличие интерфейсов картину не испортит. Что испортит? Испортит ее только неудобство работы и плохая податливость к новым хотелкам. Если вам они не мешают, то ради бога. В моих задачах — они мешают. В моих задачах, есть подобные конструкты, но они не претендуют на репозиторий. Реально, это просто выраженные в соотв используемых фреймворках транзакшн скрипты.
Не стоит забывать, что репозитории — это деталь реализации, они никак не должны сдерживать ваше воображение. А значит выполнить произвольный (например специфический для БД) запрос — вы тоже можете.

PS: Ещё раз, повторюсь, это просто мнение. Тут не с чем спорить и я не пытаюсь переубедить. Я скорее пытаюсь возвать к здравому смыслу.
Re[15]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 17.02.20 08:30
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

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

Это понятно. Просто на ассемблере придётся писать руками вообще всё, на шарпе — чуть меньше, а F#, как я понимаю, обходится без бойлерплейта.

PJ>Вообще никакой разницы. Возможет только для PaidOrder

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

PJ>Это поможет не мешать мух с котлетами, например.

Хотелось бы практических аргументов — вроде того, что сократится объём кода.

S>>Как это в каком? Появились типы ShippedPaidOrder и ShippedUnpaidOrder. А как иначе мы выразим требование "заказ перед выдачей должен быть оплачен" при помощи типов?

PJ>А ты про них. Ну да, их стало два, ну в результате у тебя получился ровно один Delivered.
У меня и до этого был один Delivered.
PJ>Может у нас разное понимание экспоненты? Ты это так любишь употреблять но порабы доказать уже математически.
PJ>Имеем,
PJ>
PJ>                                                PaidOrder                       ShippedPaidOrder -------------------------
PJ>                                              /             \                /                                             \
PJ>NewOrder -> IncompleteOrder -> ValidatedOrder                 ShippableOrder                                                 Delivered  
PJ>                                              \ UnpaidOrder /                \  ShippedUnpaidOrder -> DeliveredWithPaiment / 

PJ>

PJ>Покажи мне тут экспоненту?
Ну, вы продолжаете хитрить на ровном месте. Каким образом вы склеили PaidShippableOrder с UnpaidShippableOrder?
Если бы вы этого не сделали, то стало бы видно, как появление признака "Paid/Unpaid" удваивает количество классов. Пока что у нас получилось 2 (количество стадий оплаты) * 3 (количество стадий поставки) классов.
С каждым дополнительным признаком, который может влиять на поведение заказа, пространство типов будет умножаться на количество значений этого признака.
Итого, для N признаков мы и имеем минимум 2*2*2*...*2 N раз. Функцию F(N) = С^N мы и называем экспонентой.
Иногда рост может сдержаться за счёт того, что признаки не являются независимыми — порой мы можем выкинуть патологическое сочетание. Но так везёт не всегда. Вот вы сначала хотели схитрить и потребовать доставку только после оплаты, диагонализировав матрицу состояний. Ну, вот не везде доступна такая роскошь.

PJ>И, второй вопрос, как ты это обычно выражешь по-своему?

Обычно это выражается ровно в тех правилах, которые описывают переход состояний. В том методе, который пытается изменить статус отгрузки, проверяется наличие пре-реквизитов отгрузки. И ему всё равно, сколько ещё есть признаков и какие там у них значения. В терминах типов, этот метод полиморфен — он принимает и PaidShippableOrder, и UnpaidShippableOrder, и любой другой ShippableOrder.

PJ>А код он в C# не в типах живет? И как ты проверишь эту корретность когда поменяешь модель?

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

PJ>Ну откуда мне знать твою бизнес-задачу?! Я лишь заметил, что нет необходимости все валить в кучу, создавая God class с кучей методов.

Конечно нету. Разумную декомпозицию никто не отменял.

S>>Количество бойлерплейта радикально зависит от того, какой стиль кода выбран.

PJ>Да не особо. Тебе надо будет сделать все те же проверки, только в другом месте, и компилятор не будет тебе помогать поддерживать инвариант. Или ты под радикально другим стилем подразумеваешь спагетти-код, который вообще ничего не проверят и тупо валится при любом неверном параметре? Ну такой код будет короче, спору нет.

S>> Пока что у меня впечатление, что только лишь F# имеет (если имеет) достаточно мощную систему типов, чтобы описывать бизнес-правила с нужной точностью.

PJ>F# код моджно декомпилировать в C# код.
Ну, это хорошо. А компилятор С# будет проверять всё то же, что и F#? Ну там — полноту паттерн-матчинга? Или можно декомпилировать F# в C#, потом внести небольшое изменение, и C# всё отлично откомпилирует, а F# бы дал по рукам?
Так-то можно и в IL декомпилировать, и применить там пару трюков, которые не матчатся на валидный C#.
PJ>За исключением некоторых фич компайлера, типы там обычные дотнетовские.
Почитаем.
PJ>Несомненно на F# все это сильно проще, короче и лучше. Ну так и используй F#, если цель не страдать, а получить код который не надо отлаживать. Но если, по какой-то причине, надо использовать C#, то это не является шоустоппером.
Ну, опять же — можно всё то же самое и на ассемблере написать. Шоустоппером ничто не является. Вопрос в том, будет ли ассемблер проверять инварианты, которые проверяет F#.

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

Почитаю, почитаю.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[15]: DDD для небольших проектов.
От: takTak  
Дата: 17.02.20 08:44
Оценка:
PJ>Имеем,
PJ>
PJ>                                                PaidOrder                       ShippedPaidOrder -------------------------
PJ>                                              /             \                /                                             \
PJ>NewOrder -> IncompleteOrder -> ValidatedOrder                 ShippableOrder                                                 Delivered  
PJ>                                              \ UnpaidOrder /                \  ShippedUnpaidOrder -> DeliveredWithPaiment / 

PJ>


у меня другой вопрос по этому поводу..

спору нет, код красивше получается, чем на C#, но вот когда дело доходит до сохранения в каком-то хранилище, все mappings надо ведь, наверное, делать ручками, т.е. точно также как и при наследовании, или каждый такой класс тип будет сохраняться в отдельной таблице?

вообще, плюс C# в том, что ведь microsoft tooling на него, в основном, и направлен : генерация из entities (ef code first ), db migrations: как с этим у F# реально?
deleted
От: takTak  
Дата: 17.02.20 09:47
Оценка:
Отредактировано 17.02.2020 10:04 takTak . Предыдущая версия .
Re[34]: DDD для небольших проектов.
От: takTak  
Дата: 17.02.20 09:56
Оценка:
T>>ну раз так, то тогда попытки прилепить логику подсчёта цены к "заказу" были неправильными, "заказ" отвечает лишь за подсчёт конечной цены, ну ещё и эта коррекция цены менеджером тоже где-то должна произойти
S>Ну, вот ждём ответа от DDD — где же будет вся эта логика. И где будут храниться сами параметры ценообразования.

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

велик соблазн выделить оперaцию чтения и , соответственно, калькуляции в отдельную операцию типа чего-то такого: http://www.codinginstinct.com/2011/04/queries-aggregates-ddd.html, но всё-таки ценообразование — , поэтому надо ещё посмотреть, куда это относится

вот само управление скидками-надценками интереснее. как это сейчас реализовано? как эта информация сохраняется? т.е. это было бы реальной задачей агрегата
Re[16]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 17.02.20 10:49
Оценка:
Здравствуйте, Sinclair, Вы писали:

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

S>Это понятно. Просто на ассемблере придётся писать руками вообще всё, на шарпе — чуть меньше, а F#, как я понимаю, обходится без бойлерплейта.
Не в этом дело. Они зависимые потому, что не всякий тип создает ветвление. Если есть трансформации a -> b -> c -> d, то путь ровно один, хотя типов 4 штуки.

PJ>>Вообще никакой разницы. Возможет только для PaidOrder

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

Никакой разницы потому, что функция refund принимает только параметр PaidOrder, что логично не так ли?
Если валидность PaidOrder гарантирована, то это все что надо знать refund. Оно по-определению, в этом случае, не зависит от остальных компонентов системы.
Что, в конечном итоге, уменьшает размер кода и делает его более надежным.
Если есть валидные a, b и c, и корректные a -> b, b -> c, то функция a -> b -> c будет тоже корректна. А компилятор не даст тебе собрать d -> c.

S>У меня и до этого был один Delivered.

Ну т.е. типы не расползаются. Начал с одного и закончил одним. Какая же это экспонента?

S>Ну, вы продолжаете хитрить на ровном месте. Каким образом вы склеили PaidShippableOrder с UnpaidShippableOrder?

Что значит склеил? ShippableOrder их ко-произведение. Я использую логику процесса, как я его понимаю. На мой взгляд готовность к отправке не зависит от статуса оплаты, по твоим объяснениям.

S>Если бы вы этого не сделали, то стало бы видно, как появление признака "Paid/Unpaid" удваивает количество классов. Пока что у нас получилось 2 (количество стадий оплаты) * 3 (количество стадий поставки) классов.

А зачем бы мне это делать? Paid/Unpaid содержит разную информацию, поэтому типы разные. И дает две дополнительные функции refund : PaidOrder -> Result<> и requestPayment : PaidOrder -> Result<>
Зачем нужен второй prepareToShip я не представляю, но вполне допускаю, что смысл в этом может быть. Но, в этом случае, хоть с типами, хоть без, у тебя ровно два решения: две функции или две ветки if-else. А как еще?

PJ>>И, второй вопрос, как ты это обычно выражешь по-своему?

S>Обычно это выражается ровно в тех правилах, которые описывают переход состояний. В том методе, который пытается изменить статус отгрузки, проверяется наличие пре-реквизитов отгрузки. И ему всё равно, сколько ещё есть признаков и какие там у них значения. В терминах типов, этот метод полиморфен — он принимает и PaidShippableOrder, и UnpaidShippableOrder, и любой другой ShippableOrder.
Ты сам говорил про важность кода. Вот тут как раз такой случай.

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

Реально заначимый код это sunny day path. Все остальное это бойлерплейт. Удачи с таким кодом.

S>Ну, это хорошо. А компилятор С# будет проверять всё то же, что и F#? Ну там — полноту паттерн-матчинга? Или можно декомпилировать F# в C#, потом внести небольшое изменение, и C# всё отлично откомпилирует, а F# бы дал по рукам?

Смысл моей фразы в том, что все описанное на f# можно сделать и на c#. Я вовсе не призываю тебя писать на c#, если есть возможность писать на f#, это было бы слабоумно. Но такая возможность не всегда доступна.

PJ>>Несомненно на F# все это сильно проще, короче и лучше. Ну так и используй F#, если цель не страдать, а получить код который не надо отлаживать. Но если, по какой-то причине, надо использовать C#, то это не является шоустоппером.

S>Ну, опять же — можно всё то же самое и на ассемблере написать. Шоустоппером ничто не является. Вопрос в том, будет ли ассемблер проверять инварианты, которые проверяет F#.

Ну вот мы, например, заморочились и используем IO на f#. Разумеется чистоту функций компилятор не проверяет, приходится делать через код-ревью. Тайп-классов тоже нет.
Больше писать? Да, больше. Меньше компилятор помогает? Да, меньше.
Тем не менее возможность вынести сайд-эффекты на границы домена многократно повышеат читаемость, тестируемость и, в конечном итоге, надежность.
Дебажить практически не надо, а тех редких случаях когда приходится, ты точно знаешь место где это надо посмотреть.
То же самое и в C# или любом другом языке. Либо ты больше используешь компилятор, либо дебаггер. Как-то так...
Re[16]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 17.02.20 10:59
Оценка:
Здравствуйте, takTak, Вы писали:

T>спору нет, код красивше получается, чем на C#, но вот когда дело доходит до сохранения в каком-то хранилище, все mappings надо ведь, наверное, делать ручками, т.е. точно также как и при наследовании, или каждый такой класс тип будет сохраняться в отдельной таблице?


T>вообще, плюс C# в том, что ведь microsoft tooling на него, в основном, и направлен : генерация из entities (ef code first ), db migrations: как с этим у F# реально?


Согласно DDD, да и здравому смыслу, в таблицы ты сохраняешь отдельные dto типы. Если говорить о примере выше, будет PaidOrder и DtoPaidOrder. Что ты будешь делать с Dto это твой личный выбор, можно сделать на c# и использовать соответствующий тулинг. Хотя, в данном случае, у F# есть тайп-провайдеры и c# сильно сосет. Но, допустим, для GRPC проще держать C# проект и не парится.
Re[17]: DDD для небольших проектов.
От: takTak  
Дата: 17.02.20 11:13
Оценка:
T>>спору нет, код красивше получается, чем на C#, но вот когда дело доходит до сохранения в каком-то хранилище, все mappings надо ведь, наверное, делать ручками, т.е. точно также как и при наследовании, или каждый такой класс тип будет сохраняться в отдельной таблице?

T>>вообще, плюс C# в том, что ведь microsoft tooling на него, в основном, и направлен : генерация из entities (ef code first ), db migrations: как с этим у F# реально?


PJ>Согласно DDD, да и здравому смыслу, в таблицы ты сохраняешь отдельные dto типы. Если говорить о примере выше, будет PaidOrder и DtoPaidOrder. Что ты будешь делать с Dto это твой личный выбор, можно сделать на c# и использовать соответствующий тулинг. Хотя, в данном случае, у F# есть тайп-провайдеры и c# сильно сосет. Но, допустим, для GRPC проще держать C# проект и не парится.


я не совсем об этом: у тебя в примере для заказа получилось 6 типов:

NewOrder -> IncompleteOrder -> ValidatedOrder ShippableOrder ShippedPaidOrder ShippedUnpaidOrder,
ты для них создаёшь 6 таблиц , или например, ты пытаешься каждый раз , когда у тебя случается алгебраический тип данных (ShippedPaidOrder ShippedUnpaidOrder,), каждый раз колдовать над мэппиггами?

такое и одним типом с полем: "статус", можно, в принцип выразить, ну да ладно ...

чем какие-то провайдеры для sql лучше- тоже не совсем понятно...
Re[18]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 17.02.20 12:36
Оценка:
Здравствуйте, takTak, Вы писали:

T>я не совсем об этом: у тебя в примере для заказа получилось 6 типов:


T>NewOrder -> IncompleteOrder -> ValidatedOrder ShippableOrder ShippedPaidOrder ShippedUnpaidOrder,

T>ты для них создаёшь 6 таблиц , или например, ты пытаешься каждый раз , когда у тебя случается алгебраический тип данных (ShippedPaidOrder ShippedUnpaidOrder,), каждый раз колдовать над мэппиггами?
T>такое и одним типом с полем: "статус", можно, в принцип выразить, ну да ладно ...

Ну так и вырази, в чем проблема? Их единственная задача эффективно сохранять состояние и восстанавливать его. Как оно там сериализуется несущественно, с точки зрения DDD.

T>чем какие-то провайдеры для sql лучше- тоже не совсем понятно...

Ну тут наверно стоит прочитать, что за "какие-то провайдеры", тогда и будет понятно.
Re[19]: DDD для небольших проектов.
От: takTak  
Дата: 17.02.20 13:00
Оценка:
T>>я не совсем об этом: у тебя в примере для заказа получилось 6 типов:

T>>NewOrder -> IncompleteOrder -> ValidatedOrder ShippableOrder ShippedPaidOrder ShippedUnpaidOrder,

T>>ты для них создаёшь 6 таблиц , или например, ты пытаешься каждый раз , когда у тебя случается алгебраический тип данных (ShippedPaidOrder ShippedUnpaidOrder,), каждый раз колдовать над мэппиггами?
T>>такое и одним типом с полем: "статус", можно, в принцип выразить, ну да ладно ...

PJ>Ну так и вырази, в чем проблема? Их единственная задача эффективно сохранять состояние и восстанавливать его. Как оно там сериализуется несущественно, с точки зрения DDD.


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

T>>чем какие-то провайдеры для sql лучше- тоже не совсем понятно...

PJ>Ну тут наверно стоит прочитать, что за "какие-то провайдеры", тогда и будет понятно.

что я должен прочитать? вчера глянул у влашина в книжке: у него там голый sql в каком-то string вперемежку с каким-то sql провайдером.. это теперь круто ?!
Re[3]: DDD для небольших проектов.
От: Mr.Delphist  
Дата: 18.02.20 09:11
Оценка:
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>[cut=Смотришь на это и берет тоска]

КД>Image: 2019_02_14__nostalgia.png

Прикольно, они заморочились с интеграцией в Проводник? Если да, то зачем? Смогу ли я скопировать так накладную или текущий остаток Мартини, скажем, на флэшку или в email как аттач?
Re[35]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 21.02.20 07:03
Оценка:
Здравствуйте, takTak, Вы писали:

T>велик соблазн выделить оперaцию чтения и , соответственно, калькуляции в отдельную операцию типа чего-то такого: http://www.codinginstinct.com/2011/04/queries-aggregates-ddd.html, но всё-таки ценообразование — , поэтому надо ещё посмотреть, куда это относится


T>вот само управление скидками-надценками интереснее. как это сейчас реализовано? как эта информация сохраняется? т.е. это было бы реальной задачей агрегата

Сейчас? Сейчас у ентити "Реселлер" есть атрибут "Markup". Если он Null, то берётся дефолтный маркап, если не null — то берётся он.
Плюс есть ентити "ResellerProductGroupMarkup", которая сопоставляет реселлеру И группе наценку.
Так что для каждого товара выполняется следующий алгоритм:
1. Если есть запись в ResellerProductGroupMarkup где ResellerID = @CurrentReseller and @ProductGroupID = @ProductGroup, то берём из markup из неё.
2. Иначе, если Reseller.Markup is not null там, где Reseller.Id = @CurrentReseller, то берём его
3. Иначе берём DefaultMarkup.

Как-то так.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[17]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 21.02.20 07:23
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

PJ>Здравствуйте, Sinclair, Вы писали:


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

S>>Это понятно. Просто на ассемблере придётся писать руками вообще всё, на шарпе — чуть меньше, а F#, как я понимаю, обходится без бойлерплейта.
PJ>Не в этом дело. Они зависимые потому, что не всякий тип создает ветвление. Если есть трансформации a -> b -> c -> d, то путь ровно один, хотя типов 4 штуки.

PJ>>>Вообще никакой разницы. Возможет только для PaidOrder

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

PJ>Никакой разницы потому, что функция refund принимает только параметр PaidOrder, что логично не так ли?

PJ>Если валидность PaidOrder гарантирована, то это все что надо знать refund. Оно по-определению, в этом случае, не зависит от остальных компонентов системы.
PJ>Что, в конечном итоге, уменьшает размер кода и делает его более надежным.
PJ>Если есть валидные a, b и c, и корректные a -> b, b -> c, то функция a -> b -> c будет тоже корректна. А компилятор не даст тебе собрать d -> c.

S>>У меня и до этого был один Delivered.

PJ>Ну т.е. типы не расползаются. Начал с одного и закончил одним. Какая же это экспонента?
Кроме Delivered у нас ещё много промежуточных типов.

PJ>Что значит склеил? ShippableOrder их ко-произведение. Я использую логику процесса, как я его понимаю. На мой взгляд готовность к отправке не зависит от статуса оплаты, по твоим объяснениям.

Да, не зависит. Но мне непонятно, как вы собираетесь получать из ShippableOrder при помощи одной операции два разных типа — UnpaidShippedOrder и PaidShippedOrder.

PJ>А зачем бы мне это делать? Paid/Unpaid содержит разную информацию, поэтому типы разные. И дает две дополнительные функции refund : PaidOrder -> Result<> и requestPayment : PaidOrder -> Result<>

PJ>Зачем нужен второй prepareToShip я не представляю, но вполне допускаю, что смысл в этом может быть. Но, в этом случае, хоть с типами, хоть без, у тебя ровно два решения: две функции или две ветки if-else. А как еще?
Ну, очень просто. В плюсах это была бы частичная специализация; в C#/Java у нас была бы простая проверка предусловия. Причём, возможно, вообще не выраженного в коде приложения — была бы конфигурация предиката, которую может править администратор вообще без единого запуска компилятора. Кто ж может себе позволить ре-компиляцию и ре-деплоймент каждый раз, как надо изменить одно из правил

PJ>>>И, второй вопрос, как ты это обычно выражешь по-своему?

S>>Обычно это выражается ровно в тех правилах, которые описывают переход состояний. В том методе, который пытается изменить статус отгрузки, проверяется наличие пре-реквизитов отгрузки. И ему всё равно, сколько ещё есть признаков и какие там у них значения. В терминах типов, этот метод полиморфен — он принимает и PaidShippableOrder, и UnpaidShippableOrder, и любой другой ShippableOrder.
PJ>Ты сам говорил про важность кода. Вот тут как раз такой случай.
public static void ShipOrder(Order o)
{
   Assert(ShippingManager.IsShippingAddressValid(o.ShipmentMethod, o.ShippingAddress);
   Assert(BillingManager.IsOrderOkForDelivery(o.BillingMethod, o.Customer);
   Assert(o.ProcessingStage == ProcessingStage.ReadyForPickup);
   var pickupSchedule = ShippingManager.SchedulePickup(o);
   OrderManager.SetStage(ProcessingStage.PickupScheduled, pickupSchedule);  
}


PJ>Реально заначимый код это sunny day path. Все остальное это бойлерплейт. Удачи с таким кодом.

По моему опыту, sunny day path — это 7% кода. Как раз он и есть boilerplate. Всё остальное — это детальные политики того, что делать, когда всё пойдёт не так.
Фрод скрининг, пересортица, обработка отказов сети, обработка отказов от партнёров, вот это вот всё. Потому что happy path — в основном один, или два, а способов сломаться — много.

и C# всё отлично откомпилирует, а F# бы дал по рукам?
PJ>Смысл моей фразы в том, что все описанное на f# можно сделать и на c#. Я вовсе не призываю тебя писать на c#, если есть возможность писать на f#, это было бы слабоумно. Но такая возможность не всегда доступна.
А этот смысл — он чем-то другой, чем "все тьюринг-полные языки функционально эквивалентны"? Ну, т.е. следует ли из того, что всё, описанное на F#, можно сделать и на IL, оправданность применения IL для описания вот этих вот систем зависимых типов?

PJ>Ну вот мы, например, заморочились и используем IO на f#. Разумеется чистоту функций компилятор не проверяет, приходится делать через код-ревью. Тайп-классов тоже нет.

PJ>Больше писать? Да, больше. Меньше компилятор помогает? Да, меньше.
PJ>Тем не менее возможность вынести сайд-эффекты на границы домена многократно повышеат читаемость, тестируемость и, в конечном итоге, надежность.
PJ>Дебажить практически не надо, а тех редких случаях когда приходится, ты точно знаешь место где это надо посмотреть.
PJ>То же самое и в C# или любом другом языке. Либо ты больше используешь компилятор, либо дебаггер. Как-то так...
Не вполне понимаю всё же как именно выносить сайд-эффекты на "границы домена".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: DDD для небольших проектов.
От: okon  
Дата: 21.02.20 14:35
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте.


S>А не подскажет ли кто границы применимости DDD для проектов? Он хорошо ложится на проект любого масштаба или только для кровавого энтерпрайза?

S>Допустим для приложения типа калькулятор оно как, подойдет или слишком сложно будет? А для приложения где есть, скажем, 10-20 таблиц (сущностей) DDD
S>нормально будет? Или он только хорош, когда у нас есть n (например, n=100) сущностей?

Для небольших проектов даже отцы ДДД говорят что не надо.

Вообще все архитектурные вещи нужны для больших проектов, тогда это дает выгоду на большом объеме.

Для небольших проектов это только усложнение количество архитектурного кода и разных не нужных модулей будет намного больше.
”Жить стало лучше... но противнее. Люди которые ставят точку после слова лучше становятся сторонниками Путина, наши же сторонники делают акцент на слове противнее ( ложь, воровство, лицемерие, вражда )." (с) Борис Немцов
Re[18]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 21.02.20 17:37
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Кроме Delivered у нас ещё много промежуточных типов.

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

PJ>>Что значит склеил? ShippableOrder их ко-произведение. Я использую логику процесса, как я его понимаю. На мой взгляд готовность к отправке не зависит от статуса оплаты, по твоим объяснениям.

S>Да, не зависит. Но мне непонятно, как вы собираетесь получать из ShippableOrder при помощи одной операции два разных типа — UnpaidShippedOrder и PaidShippedOrder.
Никак это один тип: UnpaidShippedOrder | PaidShippedOrder, или OneOf<UnpaidShippedOrder, PaidShippedOrder>

PJ>>Зачем нужен второй prepareToShip я не представляю, но вполне допускаю, что смысл в этом может быть. Но, в этом случае, хоть с типами, хоть без, у тебя ровно два решения: две функции или две ветки if-else. А как еще?

S>Ну, очень просто. В плюсах это была бы частичная специализация; в C#/Java у нас была бы простая проверка предусловия.
Ну т.е. две функции и if-else. Я ровно это и сказал.

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

Т.е. администратор может изменить правила и отправить себе бесплатно посылку, без постоплаты? Тогда мы о разном говорим.

S>
S>public static void ShipOrder(Order o)
S>{
S>   Assert(ShippingManager.IsShippingAddressValid(o.ShipmentMethod, o.ShippingAddress);
S>   Assert(BillingManager.IsOrderOkForDelivery(o.BillingMethod, o.Customer);
S>   Assert(o.ProcessingStage == ProcessingStage.ReadyForPickup);
S>   var pickupSchedule = ShippingManager.SchedulePickup(o);
S>   OrderManager.SetStage(ProcessingStage.PickupScheduled, pickupSchedule);  
S>}
S>

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

PJ>>Реально заначимый код это sunny day path. Все остальное это бойлерплейт. Удачи с таким кодом.

S>По моему опыту, sunny day path — это 7% кода. Как раз он и есть boilerplate. Всё остальное — это детальные политики того, что делать, когда всё пойдёт не так.

В примере выше у тебя как раз sunny day и есть, с крэшем во всех остальных случаях.

S>Фрод скрининг, пересортица, обработка отказов сети, обработка отказов от партнёров, вот это вот всё. Потому что happy path — в основном один, или два, а способов сломаться — много.


Именно поэтому поддерживать все это в виде "политик" ага спагетти кода — крайне трудоемко и с большим количеством вероятных ошибок. Типы позволяют не пропустить не один из сценариев, сделать правильную декомпозицию и сосредоточится именно на бизнес-процессе.
Все это делается гораздо проще и надежнее. Вот почитай https://fsharpforfunandprofit.com/rop/
Хотя сам термин и как он используется в F# мне, лично, не нравится. Это просто паттерн и не обязательно про ошибки. Ну хоть так...

S> и C# всё отлично откомпилирует, а F# бы дал по рукам?


Единственное место где F# дает по рукам это неявное приведение типов. int и unit это разные типы. В остальном C# позволяет выражать все то же самое, но большим количеством кода.
Если сравнивать типичный C# код, с "правильным" F# кодом, то таки да. Руки отобьет.

PJ>>Смысл моей фразы в том, что все описанное на f# можно сделать и на c#. Я вовсе не призываю тебя писать на c#, если есть возможность писать на f#, это было бы слабоумно. Но такая возможность не всегда доступна.

S>А этот смысл — он чем-то другой, чем "все тьюринг-полные языки функционально эквивалентны"? Ну, т.е. следует ли из того, что всё, описанное на F#, можно сделать и на IL, оправданность применения IL для описания вот этих вот систем зависимых типов?

Писать на IL необходимости нет, писать на C# у многих вынужденная. Зачем сравнивать какие-то нелепости? На RSDN F# считается мертвым языком, а c# вполне себе популярен. Я не уговариваю писать на C#, но пишут.

PJ>>То же самое и в C# или любом другом языке. Либо ты больше используешь компилятор, либо дебаггер. Как-то так...

S>Не вполне понимаю всё же как именно выносить сайд-эффекты на "границы домена".
Как именно, это не про DDD, это просто паттерн IO/Actions.
Re[19]: DDD для небольших проектов.
От: Gadsky Россия  
Дата: 23.02.20 20:39
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

PJ>>>Что значит склеил? ShippableOrder их ко-произведение. Я использую логику процесса, как я его понимаю. На мой взгляд готовность к отправке не зависит от статуса оплаты, по твоим объяснениям.

S>>Да, не зависит. Но мне непонятно, как вы собираетесь получать из ShippableOrder при помощи одной операции два разных типа — UnpaidShippedOrder и PaidShippedOrder.
PJ>Никак это один тип: UnpaidShippedOrder | PaidShippedOrder, или OneOf<UnpaidShippedOrder, PaidShippedOrder>

Коллеги, проясните, OneOf<UnpaidShippedOrder, PaidShippedOrder> создает два новых типа, или таки при создании следующего этапа потребуется рантайм проверка?
Re[20]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 23.02.20 22:53
Оценка: 86 (4)
Здравствуйте, Gadsky, Вы писали:

G>Коллеги, проясните, OneOf<UnpaidShippedOrder, PaidShippedOrder> создает два новых типа, или таки при создании следующего этапа потребуется рантайм проверка?


https://github.com/mcintyre321/OneOf/
Re[19]: DDD для небольших проектов.
От: Sinclair Россия https://github.com/evilguest/
Дата: 25.02.20 04:36
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

PJ>И что? Это этого количество путей не становится экспотенциальным.

Путей — нет. Типов — да.
PJ>Каждый тип выражает некую бизнес-сущеность. Ее можно выразить через типы, можно спагетти-кодом, можно классами но как-то это сделать придеться.
Ну, хорошо, если бизнес-сущность. А если состояние бизнес сущности — то типов, на первый взгляд, может оказаться слишком много.
И без помощи компилятора (например, проверяющего полноту паттерн-матчинга) мы под их объёмом упадём.

S>>Да, не зависит. Но мне непонятно, как вы собираетесь получать из ShippableOrder при помощи одной операции два разных типа — UnpaidShippedOrder и PaidShippedOrder.

PJ>Никак это один тип: UnpaidShippedOrder | PaidShippedOrder, или OneOf<UnpaidShippedOrder, PaidShippedOrder>
Хм. Ну вы всего лишь передвинули проверку реального типа дальше по цепочке. Такой трюк выигрывает у if-else только в том случае, если компилятор отлавливает некорректности.
В C# нет поддержки OneOf, поэтому никакого улучшения по сравнению с if-else я не вижу.

PJ>Ну т.е. две функции и if-else. Я ровно это и сказал.


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

PJ>Т.е. администратор может изменить правила и отправить себе бесплатно посылку, без постоплаты? Тогда мы о разном говорим.
Конечно может. В реальных системах администратор может очень много всего. Его контролирует не система типов (это всё равно невозможно), а административные практики.
Вот у меня лично есть доступ к продакшн-системам в нескольких регионах мира. Скажем, выписать себе подписку OneDrive на десяток терабайт со скидкой 100% я могу не сходя с этого места.
Но меня удерживает а) воспитание и б) понимание, что это рано или поздно вскроется (скорость зависит от объёма усилий, которые я потрачу на заметание следов), и меня накажут.
Причём мало того, что накажут деньгами — меня обязательно уволят, и шансы получить нормальную работу сразу упадут на порядок.

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

Что такое "просто грохнуться"?
PJ>Если у тебя поменяется любое из правил, тебе придется руками найти все места где это надо исправить, делать так каждый раз и компилятор тебе ничем не поможет.
Все — это какие? Код структурирован; изменение, скажем, региональных правил валидности адресов (типа перехода от 5 к 9-значному ZIP коду) делаются в одном месте.

S>>По моему опыту, sunny day path — это 7% кода. Как раз он и есть boilerplate. Всё остальное — это детальные политики того, что делать, когда всё пойдёт не так.

PJ>В примере выше у тебя как раз sunny day и есть, с крэшем во всех остальных случаях.
Крэш в данном случае — это откат транзакции. Детали обработки каждого из фейлов спрятаны внутрь соответствующих методов.
PJ>Именно поэтому поддерживать все это в виде "политик" ага спагетти кода — крайне трудоемко и с большим количеством вероятных ошибок. Типы позволяют не пропустить не один из сценариев, сделать правильную декомпозицию и сосредоточится именно на бизнес-процессе.
PJ>Все это делается гораздо проще и надежнее. Вот почитай https://fsharpforfunandprofit.com/rop/
Почитаю.
PJ>Хотя сам термин и как он используется в F# мне, лично, не нравится. Это просто паттерн и не обязательно про ошибки. Ну хоть так...

PJ>Единственное место где F# дает по рукам это неявное приведение типов. int и unit это разные типы. В остальном C# позволяет выражать все то же самое, но большим количеством кода.

PJ>Если сравнивать типичный C# код, с "правильным" F# кодом, то таки да. Руки отобьет.
То есть F# не проверяет полноту матчей композитных типов? Тогда я не вижу способа выиграть в выразительности у классического if-else кода за счёт системы типов.

S>>Не вполне понимаю всё же как именно выносить сайд-эффекты на "границы домена".

PJ>Как именно, это не про DDD, это просто паттерн IO/Actions.
Почитаем.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[36]: DDD для небольших проектов.
От: takTak  
Дата: 25.02.20 08:03
Оценка:
T>>велик соблазн выделить оперaцию чтения и , соответственно, калькуляции в отдельную операцию типа чего-то такого: http://www.codinginstinct.com/2011/04/queries-aggregates-ddd.html, но всё-таки ценообразование — , поэтому надо ещё посмотреть, куда это относится

T>>вот само управление скидками-надценками интереснее. как это сейчас реализовано? как эта информация сохраняется? т.е. это было бы реальной задачей агрегата

S>Сейчас? Сейчас у ентити "Реселлер" есть атрибут "Markup". Если он Null, то берётся дефолтный маркап, если не null — то берётся он.
S>Плюс есть ентити "ResellerProductGroupMarkup", которая сопоставляет реселлеру И группе наценку.
S>Так что для каждого товара выполняется следующий алгоритм:
S>1. Если есть запись в ResellerProductGroupMarkup где ResellerID = @CurrentReseller and @ProductGroupID = @ProductGroup, то берём из markup из неё.
S>2. Иначе, если Reseller.Markup is not null там, где Reseller.Id = @CurrentReseller, то берём его
S>3. Иначе берём DefaultMarkup.

S>Как-то так.


это какие-то заранее агрегированные, т.е. заранее подсчитанные величины или они считаются на лету для каждого продукта в списке?

и что будет, когда , например, понадобится менять скидку/надценку в зависимости от суммы сделанных покупок или статуса покупателя?
Отредактировано 25.02.2020 8:04 takTak . Предыдущая версия .
Re[20]: DDD для небольших проектов.
От: Poopy Joe Бельгия  
Дата: 25.02.20 08:14
Оценка: 5 (1)
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Poopy Joe, Вы писали:


PJ>>И что? Это этого количество путей не становится экспотенциальным.

S>Путей — нет. Типов — да.
И типов нет. Но, допустим, что да, целях дискуссии. Если много путей, то это повышает цикломатическую сложность. А в чем проблема с типами? А функций? Чем типы отличаются от функций?

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

Как ты определяешь много или мало? И что ты делаешь в этом случае в ООП? Кладешь все в один god-class?

S>И без помощи компилятора (например, проверяющего полноту паттерн-матчинга) мы под их объёмом упадём.

Что это значит?

S>>>Да, не зависит. Но мне непонятно, как вы собираетесь получать из ShippableOrder при помощи одной операции два разных типа — UnpaidShippedOrder и PaidShippedOrder.

PJ>>Никак это один тип: UnpaidShippedOrder | PaidShippedOrder, или OneOf<UnpaidShippedOrder, PaidShippedOrder>
S>Хм. Ну вы всего лишь передвинули проверку реального типа дальше по цепочке. Такой трюк выигрывает у if-else только в том случае, если компилятор отлавливает некорректности.
S>В C# нет поддержки OneOf, поэтому никакого улучшения по сравнению с if-else я не вижу.
Че? OneOf c# тип.

PJ>>Т.е. администратор может изменить правила и отправить себе бесплатно посылку, без постоплаты? Тогда мы о разном говорим.

S>Конечно может. В реальных системах администратор может очень много всего. Его контролирует не система типов (это всё равно невозможно), а административные практики.
Система типов гарантирует инваринт системы. Ты сейчас говоришь, что инвариант системы тебя не волнует в принципе и любой залетевший дятел может сломать дом, если не опасается последствий или не знает о таковых.
Такого из начальной постановки задачи никак не следовало и мы просто говорим каждый о своем.

S>Вот у меня лично есть доступ к продакшн-системам в нескольких регионах мира. Скажем, выписать себе подписку OneDrive на десяток терабайт со скидкой 100% я могу не сходя с этого места.

S>Но меня удерживает а) воспитание и б) понимание, что это рано или поздно вскроется (скорость зависит от объёма усилий, которые я потрачу на заметание следов), и меня накажут.
S>Причём мало того, что накажут деньгами — меня обязательно уволят, и шансы получить нормальную работу сразу упадут на порядок.
Злонамеренность это одно, возможность ошибки это другое. Я разговаривал исходя из предположения, что система должна быть устойчивой. То, что требуется в большинстве случаев.

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

S>Что такое "просто грохнуться"?
У тебя что-то проверяется только в дебаге. Забыл assert или другие условия в продакшене и ты получаешь исключение.

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

S>Все — это какие? Код структурирован; изменение, скажем, региональных правил валидности адресов (типа перехода от 5 к 9-значному ZIP коду) делаются в одном месте.
При этом у тебя нет никаких гарантий, что нечто полученное под видом 9 значного кода на самом деле 9 значный код и прошло через то самое место. Или что-то не изменило его на мусор в другом месте, просто потому что параметры местами перепутались.

S>>>По моему опыту, sunny day path — это 7% кода. Как раз он и есть boilerplate. Всё остальное — это детальные политики того, что делать, когда всё пойдёт не так.

PJ>>В примере выше у тебя как раз sunny day и есть, с крэшем во всех остальных случаях.
S>Крэш в данном случае — это откат транзакции. Детали обработки каждого из фейлов спрятаны внутрь соответствующих методов.
Крэш это, в первую очеред, разрушение стека. Если ты при этом правильно откатишь транзакцию это хорошо, но это лишь вероятное следствие, причем даже правильность отката ты гарантировать не можешь. В этих местах, как правило, все утечки и случаются.

S>То есть F# не проверяет полноту матчей композитных типов? Тогда я не вижу способа выиграть в выразительности у классического if-else кода за счёт системы типов.

Откуда ты это вывел? Проверяет разумеется. И совершенно не проблема сделать то же самое в c#. Что и сделано в том же oneof, который порт dicriminated union в c#

PJ>>Как именно, это не про DDD, это просто паттерн IO/Actions.

S>Почитаем.
Не повредит, но предупреждаю, что просто почитать мало что даст. Тут надо покурить... Я еще не встречал человека, который бы осилил с первого раза. Хотя идея простая как три копейки.
Re[2]: DDD для небольших проектов.
От: Stalker. Австралия  
Дата: 26.02.20 00:37
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Наша задача — сделать правильную накрутку на базовую цену.

S>Работать должно примерно так:
S>- на все товары мы делаем фиксированную накрутку. Например, 4%. Или 7%.
S>- могут быть исключения для некоторых групп товаров — там, где нам поставщик даёт слишком маленькую скидку, мы хотим урезать наценку, иначе цена выходит дороже розницы
S>- могут быть исключения для некоторых продавцов — на их заказы мы хотим накручивать меньше.
S>- могут быть исключения для некоторых продавцов и товаров — по тем же причинам.
S>- иногда мы можем захотеть сделать скидку на конкретный заказ. Но это только если мы сами размещаем заказ от имени продавца. А когда они сами — см. требования выше.

S>Вот мне интересно, какие объекты будут появляться в DDD, и как они будут работать. Будет ли rich object model, или анемика?


эти вещи делаются при помощи rules engine, DDD к данному вопросу вообще ортогонален
Re[21]: DDD для небольших проектов.
От: Gadsky Россия  
Дата: 29.02.20 15:13
Оценка:
Здравствуйте, Poopy Joe, Вы писали:

PJ>Здравствуйте, Gadsky, Вы писали:


G>>Коллеги, проясните, OneOf<UnpaidShippedOrder, PaidShippedOrder> создает два новых типа, или таки при создании следующего этапа потребуется рантайм проверка?


PJ>https://github.com/mcintyre321/OneOf/


Ну таки это рантайм...


public TResult Match<TResult>(Func<T0, TResult> f0, Func<T1, TResult> f1)
        {
            if (_index == 0 && f0 != null)
            {
                return f0(_value0);
            }
            if (_index == 1 && f1 != null)
            {
                return f1(_value1);
            }
            throw new InvalidOperationException();
        }
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.