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.
Почитаем.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.