Информация об изменениях

Сообщение Re[10]: DDD для небольших проектов. от 14.02.2020 10:58

Изменено 14.02.2020 11:00 Poopy Joe

Re[10]: DDD для небольших проектов.
Здравствуйте, 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> Article { get; set; }    
        public Optional<Customer> Customer { get; set; }
    }
    
    public struct IncompleteOrder
    {
        public NotEmptyList<Article> Article { get; set; }
        public Customer Customer { get; set; }
        public Optional<Address> Address{ get; set; }
        public Optional<StockReference> Reserved { get; set; } 
    }
    
    public class ValidOrder
    {
        public ValidOrder(Article article, Address address, Customer customer, StockReference reference){}

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

    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 запрещает делать их из модели, поскольку они относятся к деталям бд, от которой не зависит бизнес-логика.
Re[10]: DDD для небольших проектов.
Здравствуйте, 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; set; } 
    }

    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 запрещает делать их из модели, поскольку они относятся к деталям бд, от которой не зависит бизнес-логика.