Здравствуйте, 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 есть? Или нету?