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>Как-то так.
это какие-то заранее агрегированные, т.е. заранее подсчитанные величины или они считаются на лету для каждого продукта в списке?
и что будет, когда , например, понадобится менять скидку/надценку в зависимости от суммы сделанных покупок или статуса покупателя?
Здравствуйте, 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>Почитаем.
Не повредит, но предупреждаю, что просто почитать мало что даст. Тут надо покурить... Я еще не встречал человека, который бы осилил с первого раза.
Хотя идея простая как три копейки.
Здравствуйте, 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();
}