Здравствуйте, gandjustas, Вы писали:
Z>>Воспринимай ее также в рич модели, если тебе не нужен этот паттерн, зачем его применять? G>В rich так не получится, там реальные объекты и кучи запросов.
В рич легко выполнять те же самые запросы, что и в анемик. Нет никаких препятствий для этого.
G>Только это получается не rich G>Создавая везде кастомны еобъекты для представления результатов выоборок толку от domain objects и логики в них не остается. G>Поэтому хорошее rich приложение на самом деле примерно а 95% anemic.
Рич или не рич модель теперь определяется количеством DTO требуемых для произвольных выборок? Ну ладно, можешь считать, что все пишут анемик, you win.
G>Так что не попадает null куда не надо.
Здравствуйте, Ziaw, Вы писали:
G>>Только это получается не rich G>>Создавая везде кастомны еобъекты для представления результатов выоборок толку от domain objects и логики в них не остается. G>>Поэтому хорошее rich приложение на самом деле примерно а 95% anemic.
Z>Рич или не рич модель теперь определяется количеством DTO требуемых для произвольных выборок? Ну ладно, можешь считать, что все пишут анемик, you win.
Короче, я думаю, что мы проиграли этот спор Потому что мы пользуемя anemic DTO и (о боже!) сервисами. Спорить далее не вижу смысла.
Здравствуйте, WFrag, Вы писали:
WF>Здравствуйте, gandjustas, Вы писали:
WF>>>Не совсем. События, по которым определяется список сообщений для отправки, генерирует именно наша система, обновляя ровно ту же запись, что используется в сообщениях, на основании данных нескольких внешних систем (которые, вообще говоря, могут выдавать даже противоречивые данные). G>>Ниче не понял. G>>Если у вас события отделены от состояния (например рейса), то все ок. G>>Если нет — то это нарушение SRP.
WF>Схема такая: Пришло N событий => на их основании обновляем рейс => получаем M новых событий => уже по этим событиям определяем сообщения => для каждого полученного события по рейсу (+связанные сущности типа аэропорт, перевозчик) и событию (которое содержит старое и новое значение) генерируем сообщение по шаблону.
WF>Чтобы на втором шаге обновить рейс нам просто необходимо его загрузить. Потому что обновление — это не просто update аттрибутов. Это информация типа «такой-то источник считает, что время отрыва от полосы — вот такое». Надо загрузить рейс, проанализировать его, и действовать соответственно. Можно проигнорировать обновление («noise filter» сработал), можно просто установить/обновить аттрибут, можно установить аттрибут и поменять состояние.
WF>Можно, конечно, на первом этапе грузить те аттрибуты, которые пришли в исходных событиях + ещё пара аттрибутов, а на втором ещё раз грузить рейс, но уже с теми аттрибутами, которые нужны для генерации сообщения. Но это уже два запроса.
Я уе не улавливаю всех деталей. Поэтому не смогу адекватно что-то сказать.
G>>При динамическом составлении проекции тоже кешированные данные не помогут. WF>Так речь-то о том, что как составлять проекцию и при этом делать это простым способом — мне совершенно не очевидно. Даже просто парсить шаблон и осознавать, какие колонки нам нужны — это уже добавит сложности.
Накаких сложнойстей. Код с применением EF:
//templates - шаблоны сообщенийvar parser = new Regex(@"\${\s*([^}]*)\s*}", RegexOptions.Compiled);
var fields = (from t in templates
from m in parser.Matches(t).OfType<Match>()
select m.Groups[1].Value)
.Distinct();
var projection = string.Join(", ", fields.Select(f => "it." + f).ToArray());
ObjectContext _context = new SomeContext();
var q = _context.CreateQuery<DbDataRecord>("some query");
//Filters for query
q = q.Select(projection);
var values = q.First();
var nameValueMap = fields
.Select((f, i) => new { Value = values.GetValue(i), Field = f })
.ToDictionary(p => p.Field, p => p.Value);
var texts = from t in templates
select parser.Replace(t, m => nameValueMap[m.Groups[1].Value].ToString());
Здравствуйте, Ziaw, Вы писали:
Z>Здравствуйте, gandjustas, Вы писали:
G>>Ну тут ты совсем мимо кассы. G>>Считаем обращения к связанным данным в примере синклера. Z>Пример синклера в рич модели выглядит обычно так
:
G>>В Linq такой запрос бдет выглядеть так: Z>Такой запрос пишется в жирной модели точно также и транслируется в тот же сиквел.
Да ну? И в каком месте модели пишется такой запрос? В объекте кастомера?
G>>Во-вторых сразу видно что танется из базы и можно вполне резонно задавать вопросы зачем это тянется. Z>Обращение к данным в рич модели тоже видно невооруженным глазом.
Ну да. Только тот пример, кторый написал синклер — реальность десятки таких видел, а то что ты выдумываешь — случается ооооочень редко.
G>>В-третьих — даже такой запрос выполнится быстрее (причем гораздо быстрее), чем 5n+2 из примера синклера. Z>1+N на самом деле.
Один, только что проверил.
Здравствуйте, Ziaw, Вы писали:
Z>Здравствуйте, gandjustas, Вы писали:
Z>>>Воспринимай ее также в рич модели, если тебе не нужен этот паттерн, зачем его применять? G>>В rich так не получится, там реальные объекты и кучи запросов. Z>В рич легко выполнять те же самые запросы, что и в анемик. Нет никаких препятствий для этого.
Куда запросы помещать?
G>>Так что не попадает null куда не надо. Z>Так у тебя нулл нормальное значение для данных.
Это ты так думаешь, а EF так не думает
Здравствуйте, gandjustas, Вы писали:
G>>>В-третьих — даже такой запрос выполнится быстрее (причем гораздо быстрее), чем 5n+2 из примера синклера. Z>>1+N на самом деле. G>Один, только что проверил.
Это я че-то не о том подумал....
Написал пример синклера а Linq2SQL — действительно 5n+2 получилось.
Здравствуйте, GlebZ, Вы писали:
GZ>Но для Rich, нужно сразу запастись инструментальными средствами, типа Hibernate(у которого есть чудный кэш), и средства сериализации/десериализации. Это сгладит недостатки модели.
Какое отношение к вопросу имеет ORM?
Здравствуйте, meowth, Вы писали:
M>Здравствуйте, gandjustas, Вы писали:
G>>Это я че-то не о том подумал.... G>>Написал пример синклера а Linq2SQL — действительно 5n+2 получилось.
M>Сорри, поинтересуюсь -- это доказывает, что rich и LL -- это плохо?
Да, потому что толкает к написанию тормозящего кода.
Кеш помогает скрыть проблему, но не избавляет от нее.
Здравствуйте, gandjustas, Вы писали:
M>>Сорри, поинтересуюсь -- это доказывает, что rich и LL -- это плохо? G>Да, потому что толкает к написанию тормозящего кода. G>Кеш помогает скрыть проблему, но не избавляет от нее.
Дык это как бы и есть идеология подобных ORM. Написать максимально быстро, при этом не важно как работает. Затем соптимизать критические участки до желаемой производительности.
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, gandjustas, Вы писали:
M>>>Сорри, поинтересуюсь -- это доказывает, что rich и LL -- это плохо? G>>Да, потому что толкает к написанию тормозящего кода. G>>Кеш помогает скрыть проблему, но не избавляет от нее. B>Дык это как бы и есть идеология подобных ORM. Написать максимально быстро, при этом не важно как работает. Затем соптимизать критические участки до желаемой производительности.
В случае если логика сильно опирается на LL, то оптимизация заключается в переписывании.
Помотри пример синклера выше по теме и более оптимальный вариант на Linq. Linq запрос короче, декларативнее и на порядок быстрее работает.
Поэтому rich и LL однозначно сливают.
Другое дело что в с Linq не получится работать с объектами. Уже нету кастомера в классе которого можно написать такой запрос.
Здравствуйте, gandjustas, Вы писали:
G>В случае если логика сильно опирается на LL, то оптимизация заключается в переписывании.
В случае Hibernate, вместо переписывания можно просто указать какие именно ассоциации хочется загрузить сразу же без "лени". Многие другие ORM умеют так же.
G>Помотри пример синклера выше по теме и более оптимальный вариант на Linq. Linq запрос короче, декларативнее и на порядок быстрее работает.
Возможно.
G>Поэтому rich и LL однозначно сливают.
:)
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, gandjustas, Вы писали:
G>>В случае если логика сильно опирается на LL, то оптимизация заключается в переписывании. B>В случае Hibernate, вместо переписывания можно просто указать какие именно ассоциации хочется загрузить сразу же без "лени". Многие другие ORM умеют так же.
Посмотри еще раз прмиер синклера.
Там запрос получает список категорий товаров, купленных заданным кастомером в заданный период.
Загрузкой объектов, хоть с LL, хоть без него не сильно оптимизируешь. Хотя во многих случаях eager loading помогает ускорить работу.
G>>Помотри пример синклера выше по теме и более оптимальный вариант на Linq. Linq запрос короче, декларативнее и на порядок быстрее работает. B>Возможно.
Гарантированно
Здравствуйте, Ziaw, Вы писали:
Z>Но поднимет он их, вероятно, на порядок больше чем требуется. А в рич модели тупой код будет примерно таким: Z>
Z>foreach(Order o in s.Filter(customer.Orders, "where Date between ? and ?", new[] {startDate, endDate}) // 1 запрос
Z>{
Z> foreach(String name in
Z> s.Filter(o.Items, "select Product.Category.Name where Product.Category in (?)", new[] {categories}) // +N запросов
Z> {
Z> categories.Add(name);
Z> }
Z>}
Z>
Z>Дальнейшая оптимизация нужна только если N ожидатеся реально большим.
Прекрасно. То есть мы всё-таки поднимаем все заказы за период. Поздравляю, быстродействие падает линейно с увеличением диапазона дат. Да, конечно же N ожидается реально большим. Ну, не там, где разработчик это тестирует, а в боевой базе. Кстати, как работает ваш код — что там за {categories} в параметрах, и что за фильтр вы собрались накладывать на o.Items? По-видимому, императивная запись провоцирует ошибки на ровном месте.
Z>Именно потому, что он ленив, он не будет переделывать контракт метода с foreach в который передавался кастомер. Или всетаки такие методы сами выбирают нужные для себя данные?
Ничего не понимаю. Какой контракт? В анемичной модели у метода с самого начала три параметра: идентификатор кастомера, две даты. Разработчик оставлен наедине с этими параметрами. Навигация по ордерам — последнее, что придет ему в голову. Потому, что в анемичной модели для этого придется написать вручную код, который поднимает их в память.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, WFrag, Вы писали: WF>Обновление записи — это не просто SQL-ный update. Нужно посмотреть предыдущие значения аттрибутов (и запомнить их), обновить их (прогнав каждое изменение через «noise» фильтр, обладающий правом «вето») и состояние рейса (если изменилось), получить список произошедших изменений.
Я ничего не понял, но это абсолютно неважно. Важно вот что: у вас в любом случае есть некий шаблон. Именно он определяет, что за данные потребуются. Выполнять загрузку данных до его разбора нет причины.
WF>Это условное понятие. Понятно, что кешировать можно всё, что захочется. Но так как нам не хочется возиться с когерентностью кешей и кластерными блокировками (тем более, что СУБД действительно лучше с этим справится), то там, где мы можем позволить временное нарушение консистентности (пока инвалидация пройдёт по узлам) — мы используем кеш (с любым expiration-ом, какой нам нужен), там где нет — увы.
Это всё очень хорошо, но мне по-прежнему непонятно, при чём здесь Lazy Load. Ну, кроме того, что на него можно полагаться как на fallback — там, где модель с явной загрузкой просто упадёт, LL будет делать вид, что работает — пусть и ценой чудовищной неэффективности.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, gandjustas, Вы писали:
G>Да ну? И в каком месте модели пишется такой запрос? В объекте кастомера?
Рич модель не ограничивает тебя. Запрос ты можешь поместить в сервис, маппинг или сам объект. Надо всего лишь подумать, в каком месте он будет удобнее в использовании для остального кода.
G>Ну да. Только тот пример, кторый написал синклер — реальность десятки таких видел, а то что ты выдумываешь — случается ооооочень редко.
Не нужно пытаться работать с моделью игнорируя тот факт, что это отражение персистных данных. Те, кто использует РСУБД не ноют, что почти любой сложный запрос можно написать десятком жутко тормозных способов и всего один-два дадут оптимальный план. Не ноют, что навигационный доступ в ней мягко говоря отсутствует. Что для быстроты запросов надо тюнить схему добавляя индексы, материализовывать вьюхи, денормализовывать данные. Что обрабатывать данные в циклах почти наверняка плохая идея. Большинство просто использует ее сильные стороны и радуется жизни.
Z>>1+N на самом деле. G>Один, только что проверил.
Один он если данные получать одним запросом. В обеих моделях он будет один с если включить мозг и написать его, сложность написания и там и там одинакова. N+1 будет в рич модели если включить мозг не полностью, а всего лишь следуя культуре кода.
Здравствуйте, Ziaw, Вы писали:
Z>Здравствуйте, gandjustas, Вы писали:
G>>Да ну? И в каком месте модели пишется такой запрос? В объекте кастомера?
Z>Рич модель не ограничивает тебя. Запрос ты можешь поместить в сервис, маппинг или сам объект. Надо всего лишь подумать, в каком месте он будет удобнее в использовании для остального кода.
Ну да, самый правильный rich — это anemic.
G>>Ну да. Только тот пример, кторый написал синклер — реальность десятки таких видел, а то что ты выдумываешь — случается ооооочень редко.
Z>Не нужно пытаться работать с моделью игнорируя тот факт, что это отражение персистных данных.
Ух ты. Мне недавно кто-то доказывал что PI — само главное в rich.
Определитесь какими свойствами должна обладать rich модель чтобы быть по-настоящему rich.
Здравствуйте, Sinclair, Вы писали:
WF>>Обновление записи — это не просто SQL-ный update. Нужно посмотреть предыдущие значения аттрибутов (и запомнить их), обновить их (прогнав каждое изменение через «noise» фильтр, обладающий правом «вето») и состояние рейса (если изменилось), получить список произошедших изменений. S>Я ничего не понял, но это абсолютно неважно. Важно вот что: у вас в любом случае есть некий шаблон. Именно он определяет, что за данные потребуются. Выполнять загрузку данных до его разбора нет причины.
То есть предлагается грузить данные два раза. Первый раз — для их обновления (это обязательно, так как старые значения учавствуют в расчёте новых, это то, что я пытаюсь донести до тебя и gandjustas-а, обычный update тут не сработает), второй раз для генерации сообщения по шаблону(-ам). Причём какие шаблоны потребуются тоже определяется на первых шагах — при обновлении старой записи.
Не понимаю, в чём выгода. Моё решение — не требует усилий и работает удовлетворительно. Вы на пару предлагаете какие-то усложнения без видимых преимуществ
S>Это всё очень хорошо, но мне по-прежнему непонятно, при чём здесь Lazy Load.
При том, что не нужно прописывать загрузку явно и при этом иметь достаточный контроль для оптимизации. Надо — кешируем, не надо — загружаем хоть сразу, хоть отложено.
Здравствуйте, WFrag, Вы писали:
WF>Здравствуйте, Sinclair, Вы писали:
WF>>>Обновление записи — это не просто SQL-ный update. Нужно посмотреть предыдущие значения аттрибутов (и запомнить их), обновить их (прогнав каждое изменение через «noise» фильтр, обладающий правом «вето») и состояние рейса (если изменилось), получить список произошедших изменений. S>>Я ничего не понял, но это абсолютно неважно. Важно вот что: у вас в любом случае есть некий шаблон. Именно он определяет, что за данные потребуются. Выполнять загрузку данных до его разбора нет причины.
WF>То есть предлагается грузить данные два раза. Первый раз — для их обновления (это обязательно, так как старые значения учавствуют в расчёте новых, это то, что я пытаюсь донести до тебя и gandjustas-а, обычный update тут не сработает), второй раз для генерации сообщения по шаблону(-ам). Причём какие шаблоны потребуются тоже определяется на первых шагах — при обновлении старой записи.
Любое изменение данных можно выполнить с помощью одного update без вытягивания данных. Правда этот update может быть слишком сложным.
Тогда есть хороший вариант для MS SQL — SQL CLR.
Таким образом вашу задачу модно выполнить одним (!) обращением к БД. Будет отправлен батч из двух команд — запуск нужной хранимки и выборка необходимых данных (возможно тоже хранимкой).
Только применение всех этих способов может усложнить в разы поддержку решения.
Если бы указанный этот случай был боттлнеком, то имело бы смысл заморачиваться, а если нет, то и смысла в этом нету.
WF>Не понимаю, в чём выгода. Моё решение — не требует усилий и работает удовлетворительно. Вы на пару предлагаете какие-то усложнения без видимых преимуществ
Это как раз тот случай, когда тупое решение оказывается оптимальным, и rich с anemic тут равны.
Но это в основном проблема слабых средств динамического построения запросов (особенно DML) и невозможности батчинга запросов.
Здравствуйте, Лобанов Игорь, Вы писали:
ЛИ>Есть разные варианты: ЛИ>1) Самый простой: кэш локален на каждом узле, согласованность обеспечивается синхронной инвалидацией; ЛИ>2) Для кэша используются отдельные специализированные узлы. Это стандартное решение для создания высоконагруженных приложений на платформе LAMP+memcached; ЛИ>3) Самый сложный: данные в кэше автоматически реплицируются и балансируются между узлами, что обеспечивает высокую надёжность. Ключевые слова: consistent hashing, in-memory data grid, cache partitioning. Сейчас (по крайней мере в JEE) такое умеют только дорогие проприетарные продукты типа Oracle Coherence, но на подходе и open source альтернативы.
А вот теперь объясните мне пожалуйста, ради каких таких серьёзных и неоспоримых преимуществ стоит городить весь этот огород? Когда проблема элементарно решается reverse http proxy + proxy + клиентское кэширование — если мы говорим про веб (хотите узнать как — спросите у Синклера — он объяснит, если, конечно, его не заломает объяснять это в 1000001-й раз)...