Здравствуйте, gandjustas, Вы писали:
G>Все равно не пойму в каком юзкейсе такое всречается. А то у вас data-driven в прямом смысле приложение получается, сценарии работы зависят от изменения данных. (хотя реально наоборот происходит)
Юз-кейз: оповестить пользователя о интересующем его событии. Интересующее его событие засылается клиентом в виде запроса заранее. Далее прилетают обновления данных о рейсе, нужно отреагировать соответствующим образом.
Типа: «хочу знать, когда взлетит рейс SU 1337 из Тьмутаракани 1 июня 2009, после взлёта отправьте мне сообщение XXX на номер YYY».
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, meowth, Вы писали:
S>>>Простейший пример — если есть сценарий, в котором нужно найти, к примеру, список категорий товаров, заказанных определённым покупателем за определённый период времени, то в SQL есть простой и однозначный способ описать эту задачу в терминах конкретных таблиц и связей. ORM-прослойка не даёт в этом плане ничего нового; M>>Продолжайте писать в терминах таблиц, я ж не принуждаю никого. Это вы пенитесь, что rich -- это плохо. S>Я не пенюсь. Я пытаюсь объяснить очевидные вещи людям, спорящим с тривиальностиями.
Очевидная вещь состоит в том, что современные тулы позволяют устранить "фатальные" недостатки rich-модели, заодно и переместив тучу инфраструктурного кода в инфраструктуру собственно. То, что вы не приемлете способы, которыми это достигается -- это другой вопрос. Скажем, при постановке задачи "не использовать кеш" я тоже не пользовался бы ни rich, ни и LL.
M>>Если ваш аргумент заключается в том, что rich требует кеша -- можете сразу на него забить (на аргумент). Это все равно, что спорить, что автомобиль плох, потому что требует бензин для работы. S>Аналогия плоха. Но это неважно. Если вы хотите поговорить про кэш — пожалуйста, можем поговорить о недостатках кэша. Не хотите говорить о недостатках кэша — можем поговорить о неотъемлемых проблемах Rich model.
Имхо я выше все сказал.
M>>Вы придумали задачу, я думаю здесь спорить бессмысленно, потому что там есть только то, что вы хотите. S>Конечно бессмысленно. Потому что в этой задаче анемик рвет вашу рич на тряпки. А теперь попробуйте придумать адекватный обратный пример. Окажется, что в лучшем случае рич будет играть 1:1 с анемик по объему кода, стоимости сопровождения, и эффективности реализации.
Оценить по "по объему кода, стоимости сопровождения, и эффективности реализации" можно, только поимев этот эксперимент на практике, к сожалению. Но ничего -- там gandjustas обещал написать sample. Вероятно, лучше всего будет попробовать переписать его на rich и сравнить
M>>Очень интересно, как оно будет с этими данными работать, если не загрузит их себе в память из БД. S>Интересно — поясняю: в запросе не были нужны эти данные. Перечитайте задачу еще раз. А, значит, и "работать" с ними не надо. И бояться, что кэш устарел, тоже не надо. И тратиться на обновление этого кэша — опять не надо. Ничего не надо. Всё, что надо — это список строк. И простая функция, которая отображает три параметра в список строк. Трафик между клиентом и сервером — минимален.
Если они не были нужны, то правильный LL их тоже не будет загружать.
S>Эффективность — превосходная. Повторное исполнение этого запроса будет идти в памяти СУБД, потому что все нужные страницы Order/OrderItem подняты в его кэш в любом случае — независимо от анемичности модели в аппсервере. Тот кэш, про который вы говорите — это еще одна копия тех же данных. Зачем она вам?
Поясняю -- этот кеш работает шустрее: "выборка из памяти БД" vs "выборка с включенным кешем" показывает, что из кеша приезжает в ~10-15 раз быстрее. При том, что из БД ведется только выборка, а из кеша едут уже объекты. Запрос простейший типа "select from [table]". Кеш стоит на той же машине, что и БД. Я предполагаю, что вы скажете -- "у вас неправильно настроена БД", и опровергнуть это невозможно
M>>Про реплицированный кеш я ничего не говорил пока, не путайте одно с другим. S>А вам придется про него говорить, как только нагрузка вынудит вас перевести аппсервер на кластер. Никуда вы не денетесь. Опять же, из-за того, что больше работы делается в коде апп-сервера, вам придется это делать раньше, чем анемичной модели.
Сейчас кластер. Реплицированный кеш не используется -- используется DHT, который memcached.
Здравствуйте, WFrag, Вы писали: WF>Нет, так не выйдет. Мы не знаем шаблон(-ы), пока не обновим запись.
В таком случае читать что-либо до "обновления записи", что бы это ни значило, крайне противопоказано.
WF>И самое главное, не очень понятно, зачем это нужно. Очень похоже на premature optimization. По текущим тестам, сейчас тормозят совсем не эти запросы, тем более, что поля все идут из одной строки таблицы (а остальные поля идут из кеша словарных данных)
Cам по себе кэш словарных данных — плох потому, что он явно вводит лишнее понятие "словарные данные". Где у вас граница между словарными и несловарными данными? В нормальной системе имеет место целый континуум состояний — от expiration: now до expiration: never.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, meowth, Вы писали:
M>Здравствуйте, gandjustas, Вы писали:
G>>Здравствуйте, meowth, Вы писали:
M>>>Здравствуйте, gandjustas, Вы писали:
M>>>>>Не, ты меня не понял ИМХО. Вот в одном методе бизнес логики (или сервиса в твоей интерпретации) ты виртуозным join подгрузил эти связанные сущности. (я уже молчу про то, что сервис в таком случае должен знать схему БД, чтобы стрелять joinами) G>>>>А чтобы LL делать схему знать не надо? Причем не кому-либо, а самому объекту с данными. Вот тебе и связность. M>>>Чтобы делать или не делать LL, код не нужно править. Можно отключить или включить LL в конфиге, причем для тех сущностей, которых надо. Ну и там же указать, подтягивать ли их отдельным запросом или join'ить (если это eager load). Ну и естессно, кеш работает в любом случае G>>Без составления проекци толку нету. M>Толку в чем?
В eager-load даже с кешем.
G>>Кстати как в таком случае запросы с аггрегацией работают? M>Так же, как и раньше.
Никак?
G>>>>Если два сервиса пользуются одними и теми же данными, то данные им передает вызывающий код. M>>>Как-то очень грустно получается: N сервисов связаны не только контрактом сущностей, с которыми они работают, но и общими данными с M клиентами, которые их вызывают. Причем вызывающий код должен точно знать до состава полей, какие данные понадобятся сервисам, чтобы не загрузить лишнего. А если сервис переписан и требует дополнительных полей, то придется переписать и всех его клиентов. При этом по контракту сервиса и непонятно, какие данные ему понадобятся, а какие нет -- скажем, если ему передается Order, надо ли грузить OrderItems. G>>Есть такая страная вещь как композиция. Например нужно какому либо сервсиу работать с деталями заказа, то ему передаются только детали заказа. Другому нужен order, то ему передается только он. G>>Кучка таких мелкийх сервисов собирается в более крупный, который соотвествует бизнес-транзакции типа "добавить айтем к ордеру". M>Вам счас не об этом говорят. Функциональную композицию никто не отменял, и фасадная организация a la transactional script -- это нормально. Говорят о том, что сервисы становятся хрупкими и еще код, который мог бы находиться в инфраструктуре, теперь надо пихать в сервис. G>>Соответственно внутри этого сервиса происходит все получение данных. M>Понадобятся ли они на самом деле -- это вопрос, на который нельзя ответить.
Это как раз вопрос, на который нужно отвечать.
Любая бизнес-транзакция направлена на изменение определенного набора данных.
M>>>Не, я понимаю, что такова идеология "чистый код + чистые данные раздельно" и по-другому никак, но, в общем, думаю, что это слабоватое место. G>>Это смотря как писать. Функциональная композиция рулит. M>Если писать "смотря как", то можно на любой модели сделать конфетку ФК тут не при чем. G>>А ЗД или другой код более высокого уровня образается только к сервисам которые отдают данные и не имеют побочных эффектов или передают запрос на изменение данных. (Command-Query Separation в чистом виде). M>Чтобы они отдавали данные, им нужно знать, какие данные отдавать. Причем договориться еще с тучей сервисов, что приедут именно нужные данные.
Естественно. А все эти сервисы — маленькие и их лекго тестировать и повторно использовать.
Здравствуйте, gandjustas, Вы писали:
G>какой aggregation root? Просто данные и связи между ними. G>Или это уже симптомы человека с молотком?
Дай свое определение aggregation root. Под которое не попадет Order.OrderItems.
Я вижу один большой минус в таком коде — мы должны точно знать до полей какие данные понадобятся нам в данной транзакции. И четко отслеживать изменение их состава при изменении кода. Тесты тут помогут слабо, ибо данные == dafault(T) могут означать как реальные данные поднятые из БД на которых должен работать сценарий так и данные которые просто забыли поднять.
Тестировать, что поднимаются нужные и только нужные данные означает тупо дублировать логику поднятия, надеясь, что в двух местах совершить одну и ту же ошибку будет сложнее.
Здравствуйте, WFrag, Вы писали:
WF>Здравствуйте, gandjustas, Вы писали:
G>>Все равно не пойму в каком юзкейсе такое всречается. А то у вас data-driven в прямом смысле приложение получается, сценарии работы зависят от изменения данных. (хотя реально наоборот происходит)
WF>Юз-кейз: оповестить пользователя о интересующем его событии. Интересующее его событие засылается клиентом в виде запроса заранее. Далее прилетают обновления данных о рейсе, нужно отреагировать соответствующим образом.
WF>Типа: «хочу знать, когда взлетит рейс SU 1337 из Тьмутаракани 1 июня 2009, после взлёта отправьте мне сообщение XXX на номер YYY».
Ну как я себе это представляю:
1)В системе есть какой-то внешний вход, который говорит что произошло событие типа X с параметрами y,z.
2)Есть можество подписок на разные события.
3)При наступлении события получаем все сообщения подисок, которые соотвествуют этим событиям.
4)Определяем запрос, соотвествующий этому событию.
5)Парсим все сообщения, получаем список необходимых полей.
6)Накладываем нужную проекцию на запрос.
7)Вполняем запрос, получает результаты в структуре типа словаря и генерирем нужные тексты.
Здравствуйте, Sinclair, Вы писали:
S>В таком случае читать что-либо до "обновления записи", что бы это ни значило, крайне противопоказано.
Обновление записи — это не просто SQL-ный update. Нужно посмотреть предыдущие значения аттрибутов (и запомнить их), обновить их (прогнав каждое изменение через «noise» фильтр, обладающий правом «вето») и состояние рейса (если изменилось), получить список произошедших изменений.
S>Cам по себе кэш словарных данных — плох потому, что он явно вводит лишнее понятие "словарные данные". Где у вас граница между словарными и несловарными данными? В нормальной системе имеет место целый континуум состояний — от expiration: now до expiration: never.
Это условное понятие. Понятно, что кешировать можно всё, что захочется. Но так как нам не хочется возиться с когерентностью кешей и кластерными блокировками (тем более, что СУБД действительно лучше с этим справится), то там, где мы можем позволить временное нарушение консистентности (пока инвалидация пройдёт по узлам) — мы используем кеш (с любым expiration-ом, какой нам нужен), там где нет — увы.
Здравствуйте, meowth, Вы писали:
M>Очевидная вещь состоит в том, что современные тулы позволяют устранить "фатальные" недостатки rich-модели, заодно и переместив тучу инфраструктурного кода в инфраструктуру собственно. То, что вы не приемлете способы, которыми это достигается -- это другой вопрос. Скажем, при постановке задачи "не использовать кеш" я тоже не пользовался бы ни rich, ни и LL.
Я не понимаю, куда денутся фатальные недостатки рич-модели вроде высокой связности и плохой предсказуемости взаимодействия с СУБД. Если тщательно следить за выполнением гигиены, то получим ту же анемичную модель.
M>Оценить по "по объему кода, стоимости сопровождения, и эффективности реализации" можно, только поимев этот эксперимент на практике, к сожалению. Но ничего -- там gandjustas обещал написать sample. Вероятно, лучше всего будет попробовать переписать его на rich и сравнить
Ок.
M>Если они не были нужны, то правильный LL их тоже не будет загружать.
LL, по определению, будет срабатывать в момент обращения. Да, я в курсе, что некоторые толстые ORM позволяют написать "запрос" и выполнить его без подъема промежуточных данных в кэш. Ну, разве что как правило будут подняты полные экземпляры класса Category вместо отдельных строк, но это на фоне остального ужаса — семечки.
Но в таком случае мы имеем опять же анемичную модель вид сбоку, и совершенно непонятно, ради чего вообще было городить какие-то классы ордеров с каким-то поведением.
Ну и, конечно же, чтобы получить такой случай, нужно не забывать бить по пальцам ОО-девелоперов, которые будут насиловать LL выполнением этого запроса в императивном виде:
foreach(var o in customer.Orders)
{
if (o.Date > startDate && o.Date < endDate)
{
foreach(var i in o.Items)
{
if (!categories.Contains(i.Product.Category.Name))
{
categories.Add(i.Product.Category.Name
}
}
}
}
Вот эту штуку очень легко написать; rich model и LL даже обеспечат ее работоспособность, но система в целом — ляжет. По причинам, очевидным для всех "анемистов".
M>Поясняю -- этот кеш работает шустрее: "выборка из памяти БД" vs "выборка с включенным кешем" показывает, что из кеша приезжает в ~10-15 раз быстрее. При том, что из БД ведется только выборка, а из кеша едут уже объекты. Запрос простейший типа "select from [table]". Кеш стоит на той же машине, что и БД. Я предполагаю, что вы скажете -- "у вас неправильно настроена БД", и опровергнуть это невозможно
Я скажу: вы исполняете синтетический запрос.
В жизни "select *" не нужен практически никогда. Как только вы начнёте оперировать настоящими запросами, которые потребуются в бизнес логике, статистика резко изменится в пользу СУБД. Вы легко отличите настоящие запросы от искусственных:
1. В настоящих запросах всегда стоит непустой where
2. В них всегда написан конкретный список полей перед from
Во многих случаях в запросе потребуются join. В некоторых также будут применены агрегаты и group by.
M>Сейчас кластер. Реплицированный кеш не используется -- используется DHT, который memcached.
Тогда расскажите мне, каким образом обеспечивается когерентность кэша. Может, я не понимаю чего-то очевидного?
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, gandjustas, Вы писали:
WF>>Типа: «хочу знать, когда взлетит рейс SU 1337 из Тьмутаракани 1 июня 2009, после взлёта отправьте мне сообщение XXX на номер YYY».
G>Ну как я себе это представляю: G>1)В системе есть какой-то внешний вход, который говорит что произошло событие типа X с параметрами y,z.
Не совсем. События, по которым определяется список сообщений для отправки, генерирует именно наша система, обновляя ровно ту же запись, что используется в сообщениях, на основании данных нескольких внешних систем (которые, вообще говоря, могут выдавать даже противоречивые данные).
G>2)Есть можество подписок на разные события. G>3)При наступлении события получаем все сообщения подисок, которые соотвествуют этим событиям. G>4)Определяем запрос, соотвествующий этому событию. G>5)Парсим все сообщения, получаем список необходимых полей. G>6)Накладываем нужную проекцию на запрос. G>7)Вполняем запрос, получает результаты в структуре типа словаря и генерирем нужные тексты.
В остальном всё примерно так.
Вообще, если напрячься, то можно протащить всю информацию о требуемых поля до самого начала. Непонятно только, зачем? Система усложнится довольно существенно (нужно извлекать зависимости из шаблонов, noise filter-ов (которые будут писаться вообще отдельно, а значит мы не можем гарантировать их качество), пришедших событий как таковых. А выгода мне совершенно не очевидна. Я смотрю slow queries log и вижу, что тормозят-то другие запросы.
Здравствуйте, Ziaw, Вы писали:
Z>Здравствуйте, gandjustas, Вы писали:
G>>какой aggregation root? Просто данные и связи между ними. G>>Или это уже симптомы человека с молотком?
Z>Дай свое определение aggregation root. Под которое не попадет Order.OrderItems
Придерживаюсь такого http://domaindrivendesign.org/node/88.
Order.OrderItems не для data changes, и никаких "consistency rules" у него нету.
Кроме того OrderItem.Order такое же право на существование имеет.
Вообще говоря я такую запись воспринимаю как SQL-ный JOIN по ключам, только гораздо короче.
Z>Я вижу один большой минус в таком коде — мы должны точно знать до полей какие данные понадобятся нам в данной транзакции. И четко отслеживать изменение их состава при изменении кода.
Это не минус. При работе с данными всетаки необходимо иметь представление о том что вытягивается из базы.
Z>Тесты тут помогут слабо, ибо данные == dafault(T) могут означать как реальные данные поднятые из БД на которых должен работать сценарий так и данные которые просто забыли поднять.
Это не так. default(T) в случае связанных данных это null. Интеграционные тесты отлавливают такое на ура.
Для произвольных проекций создаются анонимные или реальные объекты, если их потом нужно куда-либо передавать.
Z>Тестировать, что поднимаются нужные и только нужные данные означает тупо дублировать логику поднятия, надеясь, что в двух местах совершить одну и ту же ошибку будет сложнее.
Это обеспечивается инфраструктурой и типами. В том случае где не обеспечивается — помогает интеграционное тестирование.
Здравствуйте, Sinclair, Вы писали:
S>Я не понимаю, куда денутся фатальные недостатки рич-модели вроде высокой связности и плохой предсказуемости взаимодействия с СУБД. Если тщательно следить за выполнением гигиены, то получим ту же анемичную модель.
Запросы предсказываются не сложнее чем сиквельный результат линка.
M>>Оценить по "по объему кода, стоимости сопровождения, и эффективности реализации" можно, только поимев этот эксперимент на практике, к сожалению. Но ничего -- там gandjustas обещал написать sample. Вероятно, лучше всего будет попробовать переписать его на rich и сравнить S>Ок.
S>Но в таком случае мы имеем опять же анемичную модель вид сбоку, и совершенно непонятно, ради чего вообще было городить какие-то классы ордеров с каким-то поведением. S>Ну и, конечно же, чтобы получить такой случай, нужно не забывать бить по пальцам ОО-девелоперов, которые будут насиловать LL выполнением этого запроса в императивном виде: S>
S>foreach(var o in customer.Orders)
S>{
S> if (o.Date > startDate && o.Date < endDate)
S> {
S> foreach(var i in o.Items)
S> {
S> if (!categories.Contains(i.Product.Category.Name))
S> {
S> categories.Add(i.Product.Category.Name
S> }
S> }
S> }
S>}
S>
S>Вот эту штуку очень легко написать; rich model и LL даже обеспечат ее работоспособность, но система в целом — ляжет. По причинам, очевидным для всех "анемистов".
Здравствуйте, Sinclair, Вы писали:
M>>Очевидная вещь состоит в том, что современные тулы позволяют устранить "фатальные" недостатки rich-модели, заодно и переместив тучу инфраструктурного кода в инфраструктуру собственно. То, что вы не приемлете способы, которыми это достигается -- это другой вопрос. Скажем, при постановке задачи "не использовать кеш" я тоже не пользовался бы ни rich, ни и LL. S>Я не понимаю, куда денутся фатальные недостатки рич-модели вроде высокой связности и плохой предсказуемости взаимодействия с СУБД. Если тщательно следить за выполнением гигиены, то получим ту же анемичную модель.
Я не вижу этих фатальных недостатков, и, видимо, не пойму никогда.
M>>Оценить по "по объему кода, стоимости сопровождения, и эффективности реализации" можно, только поимев этот эксперимент на практике, к сожалению. Но ничего -- там gandjustas обещал написать sample. Вероятно, лучше всего будет попробовать переписать его на rich и сравнить S>Ок.
M>>Если они не были нужны, то правильный LL их тоже не будет загружать. S>LL, по определению, будет срабатывать в момент обращения.
Никто не спорит. Но подтягивать он будет или по одному, или пачками.
S>Да, я в курсе, что некоторые толстые ORM позволяют написать "запрос" и выполнить его без подъема промежуточных данных в кэш. Ну, разве что как правило будут подняты полные экземпляры класса Category вместо отдельных строк, но это на фоне остального ужаса — семечки.
Я этот вопрос и не затрагивал. Не надо создавать негативное ощущение перечислением недостатков, не относящихся к проблеме. Как правило -- видимо, с какого-то момента стало "как исключение".
S>Ну и, конечно же, чтобы получить такой случай, нужно не забывать бить по пальцам ОО-девелоперов, которые будут насиловать LL выполнением этого запроса в императивном виде: S>
S>foreach(var o in customer.Orders)
S>{
S> if (o.Date > startDate && o.Date < endDate)
S> {
S> foreach(var i in o.Items)
S> {
S> if (!categories.Contains(i.Product.Category.Name))
S> {
S> categories.Add(i.Product.Category.Name
S> }
S> }
S> }
S>}
S>
S>Вот эту штуку очень легко написать; rich model и LL даже обеспечат ее работоспособность, но система в целом — ляжет. По причинам, очевидным для всех "анемистов".
Мне не понятно, почему такое ляжет. Batch load вытянет вам все, что надо, в несколько запросов. Настройте так, чтобы их не было слишком много. В следующий раз коду, которому они понадобятся, все приедет из кеша.
S>Я скажу: вы исполняете синтетический запрос. S>В жизни "select *" не нужен практически никогда. Как только вы начнёте оперировать настоящими запросами, которые потребуются в бизнес логике, статистика резко изменится в пользу СУБД. Вы легко отличите настоящие запросы от искусственных: S>1. В настоящих запросах всегда стоит непустой where S>2. В них всегда написан конкретный список полей перед from S>Во многих случаях в запросе потребуются join. В некоторых также будут применены агрегаты и group by.
Ну вы же не думаете, наверное, что я исполнял именно select * from? Простейший -- в смысле, исключительно select. Использовались реальные запросы. При выборке по id запросы просто не доходят до БД, оставаясь в кеше. При сложных запросах кешируется их результат. Cached query все равно быстрее оказывается.
M>>Сейчас кластер. Реплицированный кеш не используется -- используется DHT, который memcached. S>Тогда расскажите мне, каким образом обеспечивается когерентность кэша. Может, я не понимаю чего-то очевидного?
Может быть. Что здесь неочевидного -- Вы не знаете, как организован DHT, который лочит области? Простите, я не будут тут это излагать.
Здравствуйте, WFrag, Вы писали:
WF>Здравствуйте, gandjustas, Вы писали:
WF>>>Типа: «хочу знать, когда взлетит рейс SU 1337 из Тьмутаракани 1 июня 2009, после взлёта отправьте мне сообщение XXX на номер YYY».
G>>Ну как я себе это представляю: G>>1)В системе есть какой-то внешний вход, который говорит что произошло событие типа X с параметрами y,z.
WF>Не совсем. События, по которым определяется список сообщений для отправки, генерирует именно наша система, обновляя ровно ту же запись, что используется в сообщениях, на основании данных нескольких внешних систем (которые, вообще говоря, могут выдавать даже противоречивые данные).
Ниче не понял.
Если у вас события отделены от состояния (например рейса), то все ок.
Если нет — то это нарушение SRP.
G>>2)Есть можество подписок на разные события. G>>3)При наступлении события получаем все сообщения подисок, которые соотвествуют этим событиям. G>>4)Определяем запрос, соотвествующий этому событию. G>>5)Парсим все сообщения, получаем список необходимых полей. G>>6)Накладываем нужную проекцию на запрос. G>>7)Вполняем запрос, получает результаты в структуре типа словаря и генерирем нужные тексты.
WF>В остальном всё примерно так.
WF>Вообще, если напрячься, то можно протащить всю информацию о требуемых поля до самого начала. Непонятно только, зачем? Система усложнится довольно существенно (нужно извлекать зависимости из шаблонов, noise filter-ов (которые будут писаться вообще отдельно, а значит мы не можем гарантировать их качество), пришедших событий как таковых. А выгода мне совершенно не очевидна.
Это не нужно.
Возвращаясь к исходному вопросу про кешированые связанных данных.
При самом изменении состояния рейса может и не понадобиться данных об аэропорте. (в идеале такое должно выполняться одним запросом к БД на изменение).
При динамическом составлении проекции тоже кешированные данные не помогут.
Здравствуйте, Ziaw, Вы писали: Z>Т.е. для подобных разработчиков обе модели дают одинаковые возможности для укладывания сервера.
Неправда.
Во-первых, нифига не одинаковые. Приведенный запрос быстро поднимет в память много объектов. Оригинал с LL будет выполнять миллионы мелких запросов в цикле.
Во-вторых, разработчик — ленив. Он пишет локальный минимум того, что может.
Поэтому в линке всё-таки будет
var q = from o in _orders
from i in o.Items
where o.Date > startDate && o.Date < endDate && o.CustomerId = customerId
select i.Product.Name;
Зачем он будет вставлять код, которым не пользуется?
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, gandjustas, Вы писали:
G>Придерживаюсь такого http://domaindrivendesign.org/node/88. G>Order.OrderItems не для data changes, и никаких "consistency rules" у него нету. G>Кроме того OrderItem.Order такое же право на существование имеет. G>Вообще говоря я такую запись воспринимаю как SQL-ный JOIN по ключам, только гораздо короче.
Воспринимай ее также в рич модели, если тебе не нужен этот паттерн, зачем его применять?
Z>>Я вижу один большой минус в таком коде — мы должны точно знать до полей какие данные понадобятся нам в данной транзакции. И четко отслеживать изменение их состава при изменении кода. G>Это не минус. При работе с данными всетаки необходимо иметь представление о том что вытягивается из базы.
Миф о том, что в рич модели программист не имеет представления о вытягиваемых данных преувеличен.
G>Это не так. default(T) в случае связанных данных это null. Интеграционные тесты отлавливают такое на ура.
Я говорю о связях 0..1, т.е. null вполне адекватное представление данных, а у тебя он является еще и флагом !IsLoaded.
G>Для произвольных проекций создаются анонимные или реальные объекты, если их потом нужно куда-либо передавать.
Как и в рич модели
G>Это обеспечивается инфраструктурой и типами. В том случае где не обеспечивается — помогает интеграционное тестирование.
Не понял, как выглядит тест на то, что все нужные данные для транзакции поднимаются.
Здравствуйте, gandjustas, Вы писали:
WF>>Не совсем. События, по которым определяется список сообщений для отправки, генерирует именно наша система, обновляя ровно ту же запись, что используется в сообщениях, на основании данных нескольких внешних систем (которые, вообще говоря, могут выдавать даже противоречивые данные). G>Ниче не понял. G>Если у вас события отделены от состояния (например рейса), то все ок. G>Если нет — то это нарушение SRP.
Схема такая: Пришло N событий => на их основании обновляем рейс => получаем M новых событий => уже по этим событиям определяем сообщения => для каждого полученного события по рейсу (+связанные сущности типа аэропорт, перевозчик) и событию (которое содержит старое и новое значение) генерируем сообщение по шаблону.
Чтобы на втором шаге обновить рейс нам просто необходимо его загрузить. Потому что обновление — это не просто update аттрибутов. Это информация типа «такой-то источник считает, что время отрыва от полосы — вот такое». Надо загрузить рейс, проанализировать его, и действовать соответственно. Можно проигнорировать обновление («noise filter» сработал), можно просто установить/обновить аттрибут, можно установить аттрибут и поменять состояние.
Можно, конечно, на первом этапе грузить те аттрибуты, которые пришли в исходных событиях + ещё пара аттрибутов, а на втором ещё раз грузить рейс, но уже с теми аттрибутами, которые нужны для генерации сообщения. Но это уже два запроса.
G>Возвращаясь к исходному вопросу про кешированые связанных данных. G>При самом изменении состояния рейса может и не понадобиться данных об аэропорте. (в идеале такое должно выполняться одним запросом к БД на изменение).
Да, так и есть. Одним запросом и обновляется.
G>При динамическом составлении проекции тоже кешированные данные не помогут.
Так речь-то о том, что как составлять проекцию и при этом делать это простым способом — мне совершенно не очевидно. Даже просто парсить шаблон и осознавать, какие колонки нам нужны — это уже добавит сложности.
Здравствуйте, Ziaw, Вы писали:
S>>Но в таком случае мы имеем опять же анемичную модель вид сбоку, и совершенно непонятно, ради чего вообще было городить какие-то классы ордеров с каким-то поведением. S>>Ну и, конечно же, чтобы получить такой случай, нужно не забывать бить по пальцам ОО-девелоперов, которые будут насиловать LL выполнением этого запроса в императивном виде: S>>
S>>foreach(var o in customer.Orders)
S>>{
S>> if (o.Date > startDate && o.Date < endDate)
S>> {
S>> foreach(var i in o.Items)
S>> {
S>> if (!categories.Contains(i.Product.Category.Name))
S>> {
S>> categories.Add(i.Product.Category.Name
S>> }
S>> }
S>> }
S>>}
S>>
S>>Вот эту штуку очень легко написать; rich model и LL даже обеспечат ее работоспособность, но система в целом — ляжет. По причинам, очевидным для всех "анемистов".
Ну тут ты совсем мимо кассы.
Считаем обращения к связанным данным в примере синклера.
Поднять кастомера — 1
customer.Orders — вытягивание всех ордеров — 1, предположим что в условие o.Date > startDate && o.Date < endDate попадает n штук.
o.Items — вытягивание всех айтемом — 1*n
i.Product.Category.Name — два запроса (один для Product, один для Category), в двух строчках такой кайф встречается.
Итого 1+1+n+4n = 5n+2 запросов. Соотвественно это приведет к поднятию в кеш хотябы один раз множества объектов Product и Category.
В Linq такой запрос бдет выглядеть так:
(from o in Orders
where Order.Customer.Id = custId //в SQL даже не будет ображения к таблице кастомеровwhere o.Date > startDate
where o.Date < endDate
from item in o.Items
select item.Product.Category.Name
).Distinct()
При наличии индексов по дате ордера, ордеры не попадающие в диапазон дат даже не будут пондняты с диска. А продукты и категории будут считаны ровно один раз.
Ну и кроме всего прочего это будет один запрос, а не 5n+2.
Z>Такому разработчику никто не запретит написать перед этим в анемичной модели от ганжустаса
Z>var q = from o in _orders.With(ord => ord.OrderItems).With(item => item.Product).With(product => product.Category)
Z> where o.Id == someId
Z> select o;
Z>
Z>Т.е. для подобных разработчиков обе модели дают одинаковые возможности для укладывания сервера.
Во-первых код будет такой:
var q = from o in _orders.With(ord => ord.OrderItems).With("OrderItems.Product.Category")
where o.Id == someId
select o;
Во-вторых сразу видно что танется из базы и можно вполне резонно задавать вопросы зачем это тянется.
В-третьих — даже такой запрос выполнится быстрее (причем гораздо быстрее), чем 5n+2 из примера синклера.
Здравствуйте, Sinclair, Вы писали:
S>Во-первых, нифига не одинаковые. Приведенный запрос быстро поднимет в память много объектов. Оригинал с LL будет выполнять миллионы мелких запросов в цикле.
Но поднимет он их, вероятно, на порядок больше чем требуется. А в рич модели тупой код будет примерно таким:
foreach(Order o in s.Filter(customer.Orders, "where Date between ? and ?", new[] {startDate, endDate}) // 1 запрос
{
foreach(String name in
s.Filter(o.Items, "select Product.Category.Name where Product.Category in (?)", new[] {categories}) // +N запросов
{
categories.Add(name);
}
}
Дальнейшая оптимизация нужна только если N ожидатеся реально большим.
S>Во-вторых, разработчик — ленив. Он пишет локальный минимум того, что может. S>Поэтому в линке всё-таки будет S>
S>var q = from o in _orders
S> from i in o.Items
S> where o.Date > startDate && o.Date < endDate && o.CustomerId = customerId
S> select i.Product.Name;
S>
S>Зачем он будет вставлять код, которым не пользуется?
Именно потому, что он ленив, он не будет переделывать контракт метода с foreach в который передавался кастомер. Или всетаки такие методы сами выбирают нужные для себя данные?
Такой запрос пишется в жирной модели точно также и транслируется в тот же сиквел.
G>Во-вторых сразу видно что танется из базы и можно вполне резонно задавать вопросы зачем это тянется.
Обращение к данным в рич модели тоже видно невооруженным глазом.
G>В-третьих — даже такой запрос выполнится быстрее (причем гораздо быстрее), чем 5n+2 из примера синклера.
Здравствуйте, Ziaw, Вы писали:
Z>Здравствуйте, gandjustas, Вы писали:
G>>Придерживаюсь такого http://domaindrivendesign.org/node/88. G>>Order.OrderItems не для data changes, и никаких "consistency rules" у него нету. G>>Кроме того OrderItem.Order такое же право на существование имеет. G>>Вообще говоря я такую запись воспринимаю как SQL-ный JOIN по ключам, только гораздо короче.
Z>Воспринимай ее также в рич модели, если тебе не нужен этот паттерн, зачем его применять?
В rich так не получится, там реальные объекты и кучи запросов.
Z>>>Я вижу один большой минус в таком коде — мы должны точно знать до полей какие данные понадобятся нам в данной транзакции. И четко отслеживать изменение их состава при изменении кода. G>>Это не минус. При работе с данными всетаки необходимо иметь представление о том что вытягивается из базы. Z>Миф о том, что в рич модели программист не имеет представления о вытягиваемых данных преувеличен.
ну-ну.
G>>Это не так. default(T) в случае связанных данных это null. Интеграционные тесты отлавливают такое на ура. Z>Я говорю о связях 0..1, т.е. null вполне адекватное представление данных, а у тебя он является еще и флагом !IsLoaded.
Ну вообще говоря и это неверно. EF напрмиер выдает вполне корректный exception если не загружена связь.
G>>Для произвольных проекций создаются анонимные или реальные объекты, если их потом нужно куда-либо передавать. Z>Как и в рич модели
Только это получается не rich
Создавая везде кастомны еобъекты для представления результатов выоборок толку от domain objects и логики в них не остается.
Поэтому хорошее rich приложение на самом деле примерно а 95% anemic.
G>>Это обеспечивается инфраструктурой и типами. В том случае где не обеспечивается — помогает интеграционное тестирование. Z>Не понял, как выглядит тест на то, что все нужные данные для транзакции поднимаются.
Так что не попадает null куда не надо.