Здравствуйте, Vladek, Вы писали:
V>А как ты видишь эту проблему? Запускаешь профайлер, видишь по логам проседание производительности? Пользователи жалуются? Или тебе просто кажется, что вот этот кусок кода "неэффективный"? Если последнее, то рекомендую пить успокоительное.
)
Вот, к примеру, какое-нибудь матёрое приложение выполняет апгрейд базы со старой на новую версию 30 часов у кастомера приличного объёма. Вице-президент волевым усилием выдвигает требование: "вы должны уложиться в 8 часов!"
Ну, потому что инженерам поддержки неудобно, когда апгрейд начинает одна смена, а заканчивает другая.
Профайлер, есессно, никто не подключает — к чему его подключать? Мы и так знаем, чем занимается система — поднимаем объекты в память, подправляем, записываем обратно. И так 20 миллионов раз.
Пользователи — не жалуются. "Чо ж вы хотели — 30 гигов, как-никак!" Сравнить им, бедняжкам, не с чем — переезд на другую систему будет стоить полгода и пару миллионов долларов. Не то, чем хочется заняться в выходные.
В итоге инженеры, поохав про то, что "ну никак невозможно! Мы там процентов 10 сможем отжать, и всё!" выкидывают нахрен ORM из апгрейд-процесса, аккуратно переписывают всё на SQL, и апгрейд укладывается в 1 час 40 минут.
Конечно, после такого приходится пить успокоительное. Ну, чтоб в табло не заехать тем, кто уверял про "никак невозможно".
V>Смысл использования фабрик и репозиториев в создании переносимого ядра приложения. Под переносимостью я тут понимаю способность написанного кода работать в разных окружениях — в вебе, в консоли, на десктопе, в службе на сервере. Детали реализации будут меняться, а бизнес-логика (код в ядре) будет оставаться прежним.
Проект, в котором было нужно переносить бизнес-логику между сервером, десктопом, и консолью, я встречал в последний раз... дайте вспомнить... ага, точно: никогда.
Я системы автоматизации бизнеса за деньги, если чо, пишу с 1991 года. Работал и в бодишопе, где каждые два месяца — новый проект. То есть не то, чтобы у одного станка 25 лет простоял.
Всё жду: может встретится когда-нибудь такая штука?
А вот неэффективностей на ровном месте я встречаю ежедневно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Vladek, Вы писали:
V>>> Агрегаты достаются из репозитория только для внесения изменений (запись). AVK>>Зачем что то сперва доставать для внесения изменений? V>Я использую фабрики или оператор new, чтобы создать объект, которого раньше не существовало, и репозитории, чтобы достать объект, который уже где-то есть. Это просто, это логично, это работает.
Но вопрос то был в другом. Неужели тяжелые ORM так затуманивают взгляд, что даже очевидные проблемы ускользают от внимания?
Теперь я кажется понял, почему в соседнем топике не захотели видеть проблему в том, что ради одного поля из БД достается целая сущность.
Пользователи тяжелых ОРМ похоже просто не понимают, что можно сделать иначе.
Подобные статьи появляются с периодичностью раз в полгода.
И эти идеи всегда натыкаются на одни и те же грабли.
Грабля номер раз: проекция. Если твой метод возвращает IEnumerable<T>, где T — сущность замапленная на таблицу, то твой код — тормозное говно. Мало того, что на каждое обращение тянутся целые объекты, так и еще и проблемы возникают когда надо вывести список заказов с именами и адресами клиентов.
Грабля номер два: композиция. Очень наивно думать, что все запросы будут по id или фильтром по одному полю. В приложении постоянно появляются дополнительные условия фильтрации. Даже для блогов надо показывать\скрывать неопубликованные посты и комментарии, а в реальных бизнес-приложениях еще и права пользователей учитывать надо. По факту в процессе развития приложения запросы постоянно усложняются (я не видел чтобы было по-другому) поэтому нужен удобный способ собирать запрос по кускам, чтобы в нужном месте можно было добавить предикаты.
Грабля номер три, она появится сразу после решения двух проблем выше: вы изобретаете свой IQueryable. Все что вы хотите уже есть в EF и других ORM. Изобретение того же, но своего нужно только с одной целью — понять что так делать не стоит.
Здравствуйте, Vladek, Вы писали:
V>Репозитории хранят главные объекты предметной области (агрегаты) между запусками программы.
Почему главные объекты предметной области обязательно агрегаты?
V> Агрегаты достаются из репозитория только для внесения изменений (запись).
Зачем что то сперва доставать для внесения изменений?
V> Агрегаты сами себя умеют записывать в бд или куда им это надо.
Поштучно?
V> Для чтения вся эта лабуда не используется, а используются шлюзы, возвращающие простые структуры данных, пригодные для показа пользователю, отправке через сеть, ещё куда; самые настоящие DTO, они же Model в MVC-фреймворье.
А если вдруг транзакция и на чтение и на запись вместе?
V>ORM используется на самом нижнем уровне, не вылезая за пределы репозиториев, шлюзов и конкретных экземпляров агрегатов
Т.е. на каждую операцию дравствую полная тележка boilerplate кода?
V>Никакие IQueryable наружу не торчат, каждый LINQ-запрос к ORM встречается один раз и весь сразу
Т.е. на каждый запрос заводим как минимум один метод?
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Max Mustermann, Вы писали:
MM>Какая прохладная история. Я-то думал, что "пишу всё на хранимках с 91-го — хорошо себя чуствую" уже перевелись, а подиж-ты, жива старая гвардия!
А новая гвардия что делает? Пишет мегарешение, которое кушает 5 килобаксов в месяц за облако, выполняя при этом малую долю работы похожей системы, тратящей 1.5К в месяц? При том что и у второй системы не все идеально и с перфомансом, и с лишним бойлерплейтом.
Если что — это как раз реальный пример применения модных технологий и CQRS.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Shmj, Вы писали:
S>Приглашаю высказать свое фи: https://habrahabr.ru/post/335856/
Приставка "анти" не перед тем словом стоит, должна быть перед EF =)
[Skip]
Au>А зачем нужна транзакция для юнит теста? Или используется реальная тестовая БД?
Я, например, как правило для CI имею бакап эталонной базы, она подымается и тесты проганяются на этой базе. Это автоматически спасает от кучи сторонних эффектов (тригера, индексы, констрейнты, типы данных).
Да тесты работают медленней, зато в продакшине я спокоен.
Прочитал по диагонали. Подход знаком, это просто очень тонкая обёртка на ORM. Надо либо перестать стесняться и использовать ORM везде напрямую или прятать его глубже. Обёртка — это просто лишний код, который только мешается. В статье TL;DR написан правильный, однако самой статье он противоречит.
Использую следующий подход и написанный код продолжает мне нравится даже недели и месяцы спустя после написания.
Репозитории хранят главные объекты предметной области (агрегаты) между запусками программы. Агрегаты достаются из репозитория только для внесения изменений (запись). Агрегаты сами себя умеют записывать в бд или куда им это надо. Для чтения вся эта лабуда не используется, а используются шлюзы, возвращающие простые структуры данных, пригодные для показа пользователю, отправке через сеть, ещё куда; самые настоящие DTO, они же Model в MVC-фреймворье.
ORM используется на самом нижнем уровне, не вылезая за пределы репозиториев, шлюзов и конкретных экземпляров агрегатов (наследников знающих про ORM). Никакие IQueryable наружу не торчат, каждый LINQ-запрос к ORM встречается один раз и весь сразу, ничего там позже динамически не подгружается, не модифицируется, никаких сюрпризов. Вот запрос, с любыми нужными джойнами и условиями, вот его результат здесь же маппится в DTO или ещё как используется. Не нужно запускать отладчик и прыгать по всему проекту, выясняя почему запрос не такой как надо.
), по дурости вступая в споры с какими-то людьми, кода которых я не видел и они сами его не показывали. Нет кода, нечего и обсуждать. Простой пример описанного подхода (без модели предметной области, только шлюзы): https://github.com/vborovikov/rsdn
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Vladek, Вы писали:
V>>А как ты видишь эту проблему? Запускаешь профайлер, видишь по логам проседание производительности? Пользователи жалуются? Или тебе просто кажется, что вот этот кусок кода "неэффективный"? Если последнее, то рекомендую пить успокоительное. S>) S>Вот, к примеру, какое-нибудь матёрое приложение выполняет апгрейд базы со старой на новую версию 30 часов у кастомера приличного объёма. Вице-президент волевым усилием выдвигает требование: "вы должны уложиться в 8 часов!" S>Ну, потому что инженерам поддержки неудобно, когда апгрейд начинает одна смена, а заканчивает другая. S>Профайлер, есессно, никто не подключает — к чему его подключать? Мы и так знаем, чем занимается система — поднимаем объекты в память, подправляем, записываем обратно. И так 20 миллионов раз. S>Пользователи — не жалуются. "Чо ж вы хотели — 30 гигов, как-никак!" Сравнить им, бедняжкам, не с чем — переезд на другую систему будет стоить полгода и пару миллионов долларов. Не то, чем хочется заняться в выходные.
S>В итоге инженеры, поохав про то, что "ну никак невозможно! Мы там процентов 10 сможем отжать, и всё!" выкидывают нахрен ORM из апгрейд-процесса, аккуратно переписывают всё на SQL, и апгрейд укладывается в 1 час 40 минут. S>Конечно, после такого приходится пить успокоительное. Ну, чтоб в табло не заехать тем, кто уверял про "никак невозможно".
И что? Я тут должен защищать использование ORM? Я уже написал, повторяю:
Можно делать как угодно. Руководствоваться надо здравым смыслом. Ни один подход не работает везде.
А превращение дискуссии в соревнование суровых сибирских мужиков на испытание японской пилы я уже наблюдал неоднократно в похожих темах. Вообще-то только это и наблюдаю. Предлагаешь подход к архитектуре типичного корпоративного софта, вылезают какие-то ветераны, начинают критиковать, ничего интересного взамен не предлагают, кода своего почитать не дают. Что я должен полезного из этого извлечь? Ничего я не извлекаю.
V>>Смысл использования фабрик и репозиториев в создании переносимого ядра приложения. Под переносимостью я тут понимаю способность написанного кода работать в разных окружениях — в вебе, в консоли, на десктопе, в службе на сервере. Детали реализации будут меняться, а бизнес-логика (код в ядре) будет оставаться прежним. S>Проект, в котором было нужно переносить бизнес-логику между сервером, десктопом, и консолью, я встречал в последний раз... дайте вспомнить... ага, точно: никогда.
А я встречал. Зато никогда не приходилось возиться с бд в 30 Гб. Делает это мой опыт менее ценным? Нет, не думаю — кому-то пригодится. Делает это твой опыт более ценным — не знаю, ты им не делишься.
Show me the source.
S>Я системы автоматизации бизнеса за деньги, если чо, пишу с 1991 года. Работал и в бодишопе, где каждые два месяца — новый проект. То есть не то, чтобы у одного станка 25 лет простоял. S>Всё жду: может встретится когда-нибудь такая штука?
У тебя код программ в публичном доступе есть? Почитаю на досуге, поучусь у мэтра.
S>А вот неэффективностей на ровном месте я встречаю ежедневно.
В коде я в первую очередь стараюсь уменьшить сложность, тогда и с неэффективностью бороться легче будет.
Здравствуйте, Vladek, Вы писали:
V>И что? Я тут должен защищать использование ORM?
Не, не должен. V>Предлагаешь подход к архитектуре типичного корпоративного софта, вылезают какие-то ветераны, начинают критиковать, ничего интересного взамен не предлагают, кода своего почитать не дают. Что я должен полезного из этого извлечь? Ничего я не извлекаю.
Эпик срач Рич vs Анемик поднимается тут каждые три года. Лично я все "взамен" привёл ещё в предыдущих двух, в третий раз уже не хватает усердия. V>А я встречал. Зато никогда не приходилось возиться с бд в 30 Гб. Делает это мой опыт менее ценным? Нет, не думаю — кому-то пригодится. Делает это твой опыт более ценным — не знаю, ты им не делишься.
Ну почему же не делюсь? Делюсь. Вот прямо в топике и делюсь. Подобных историй я могу привести не одну. V>У тебя код программ в публичном доступе есть? Почитаю на досуге, поучусь у мэтра.
Нет, я работаю за деньги. Мои труды никто в open source пока не переводит. S>>А вот неэффективностей на ровном месте я встречаю ежедневно. V>В коде я в первую очередь стараюсь уменьшить сложность, тогда и с неэффективностью бороться легче будет.
Это хорошо. Но заменить код, написанный через "агрегаты" и Lazy Load (ну, это где User сам себе скидку начисляет и письмо отправляет по SMTP) на нормальный — очень тяжко.
Единственный плюс в такой "компактной бизнес-логике" — в том, что её не так жалко выкидывать, когда целый кусок переезжает в анемик.
Ну, как в примере с инсталлятором апдейта — там кода, который тратил 90% времени, было совсем немного.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Max Mustermann, Вы писали: MM>Если вкратце — то всё это хорошо для тех, кто сложнее northwind баз не пользовал. А если выглянуть за пределы уютненького, то внезапно(!) окажется, что в процессе "подправляем" еще нужны данные из друго источника, поднимаются какие-то тонны правил в WF и попутно вызываются пара библиотек, которые в движок БД не завезли. Упс.
Конкретно при апгрейде ситуация "надо слазить в другой источник" бывает крайне редко. И повычислять что-нибудь экзотическое тоже надо очень редко.
MM>Поэтому разговор с вменяемыми базовиками на эту тему обычно напоминает старый анекдот "ты сильный, ты сможешь — не, я умный, я даже не возьмусь". С невменяемыми примерно то же самое, но они сначала берут месяц-два на "аккуратно переписывают всё на SQL", потом много "пить успокоительное" и так трогательно заглядывая в глаза "а у вас нет более другой ORM, вот такой же, но быстнее? Смотрите, уже быстрее, на целых 10%! Вот видите, есть же, а говорили, что не сможете!.. А, ну да.".
Отлично, коллега. Я вижу, вы уже созрели для освоения современных инструментов, которые всё ещё вдесятеро быстрее, чем lazy load, но при этом не страдают ограничениями SQL.
MM>Собсна весь конфликт вращается вокруг этого самого: люди, у которых вся "логика" помещается в "update where" не понимают проблем тех, у которых перед update запускается ядерный реактор. MM>Вобщем это два разных Шапиро и вместе им не сойтись.
Я бы посоветовал вам ознакомиться с linq2db. На тему схождения несходимого. MM>Но вообще в треде на тему делать ли обёртки поверх EF богатая идея "выкидывают нахрен ORM и аккуратно переписывают всё на SQL" — это успех однозначно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Au, Вы писали:
AVK>> А зачем? Au>Ты привел некоторое решение, хочется видеть насколько оно эффективно с точки зрения полного цикла разработки/поддержки (то есть с тестами).
Это в принципе невозможно без подробносьей.
AVK>> Там ничего удивительного нет, прогоняются юзкейсы основные. И без подробностей все равно пользы от них не будет. Au>Ничего удивительного и не нужно, хочется увидеть как тестируется твое решение.
Совершенно обычным способом. Берется входной набор данных, покрывающий основные сценарии, успешные и сбойные, и прогоняется по этому коду.
Au> Покажи 1 тест, например который проверяет что ассерт SerialNumberAlreadyUsedForUser срабатывает при нужных условиях.
Берем валидный во всех отношениях серийник, прогоняем. Потом прогоняем еще раз и смотрим код ошибки.
[TestCase("****", 1)]
[TestCase("****", 4)]
public async Task ActivateActivated(string serialNumber, int maxActivations)
{
using (var db = _dbFactory())
using (var tx = db.BeginTransaction())
{
for (var i = 0; i < maxActivations; i++)
await DoActivation(db, serialNumber);
var ex = Assert.ThrowsAsync<HttpServiceException>(async () => await DoActivation(db, serialNumber));
Assert.AreEqual(HttpStatusCode.BadRequest, ex.StatusCode);
Assert.AreEqual("SerialNumberAlreadyUsed", ex.ErrorCode);
tx.Rollback();
}
}
Какие тут еще могут быть варианты?
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Слава, Вы писали:
С>Здравствуйте, AngeL B., Вы писали:
AB>>P.S. Я не оправдываю Repository, т.к. пытался использовать его в реальных проектах и быстро пришел к тем же заключениям, которые были описаны выше (короче отказался). Я говорю, что указывать просто затраты на облако с точки зрения бизнеса некорректно.
С>Лично мне проще пользоваться запросами, чем вот этой необыкновенной ерундой с ORM и развесистой иерархией репозов, которые сами не делают ничего. Вообще, обилие разного рода SQL-бабок, т.е. женщин средних лет, которые занимаются только БД и которым платят не столь много, как бы говорит нам, что уметь пользоваться запросами — это несложно.
Query Decomposition — вот что голые запросы тебе дать не могут. Получится самопальная склейка строк.
Применение спецификаций не исключает использование Repository, можно и то и другое использовать. Только в этом случае спецификации должны быть инкапсулированы в Repository.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, Vladek, Вы писали:
V>>Репозитории хранят главные объекты предметной области (агрегаты) между запусками программы.
AVK>Почему главные объекты предметной области обязательно агрегаты?
Потому что, как правило, они моделируют некую относительно сложную сущность, состоящую из отдельных малых частей. Следовательно, агрегат.
V>> Агрегаты достаются из репозитория только для внесения изменений (запись).
AVK>Зачем что то сперва доставать для внесения изменений?
Я использую фабрики или оператор new, чтобы создать объект, которого раньше не существовало, и репозитории, чтобы достать объект, который уже где-то есть. Это просто, это логично, это работает.
V>> Агрегаты сами себя умеют записывать в бд или куда им это надо.
AVK>Поштучно?
Поштучно, с помощью репозитория пучком если именно так надо.
V>> Для чтения вся эта лабуда не используется, а используются шлюзы, возвращающие простые структуры данных, пригодные для показа пользователю, отправке через сеть, ещё куда; самые настоящие DTO, они же Model в MVC-фреймворье.
AVK>А если вдруг транзакция и на чтение и на запись вместе?
Радуюсь, что мне никогда такое не встречалось. Нужен конкретный пример, где действия пользователя нельзя чётко разделить на запросы и команды.
V>>ORM используется на самом нижнем уровне, не вылезая за пределы репозиториев, шлюзов и конкретных экземпляров агрегатов
AVK>Т.е. на каждую операцию дравствую полная тележка boilerplate кода? AVK>Т.е. на каждый запрос заводим как минимум один метод?
Стереотипный код писать я тоже не люблю. Любой репозиторий и любой объект предметной области использует ORM целиком и делает столько запросов сколько ему надо. Мне важнее другое — вот папка, где лежит код работы с ORM. Я могу его прочитать весь сразу, я могу его поменять как угодно, и мне не надо особо трогать файлы и классы за пределами этой папки.
Здравствуйте, sergeya, Вы писали:
S>Здравствуйте, Vladek, Вы писали:
V>>>> Агрегаты достаются из репозитория только для внесения изменений (запись). AVK>>>Зачем что то сперва доставать для внесения изменений? V>>Я использую фабрики или оператор new, чтобы создать объект, которого раньше не существовало, и репозитории, чтобы достать объект, который уже где-то есть. Это просто, это логично, это работает.
S>Но вопрос то был в другом. Неужели тяжелые ORM так затуманивают взгляд, что даже очевидные проблемы ускользают от внимания? S>Теперь я кажется понял, почему в соседнем топике не захотели видеть проблему в том, что ради одного поля из БД достается целая сущность.
А как ты видишь эту проблему? Запускаешь профайлер, видишь по логам проседание производительности? Пользователи жалуются? Или тебе просто кажется, что вот этот кусок кода "неэффективный"? Если последнее, то рекомендую пить успокоительное. Если другое — код конечно надо переписывать. Ниже выделено жирным — почему.
S>Пользователи тяжелых ОРМ похоже просто не понимают, что можно сделать иначе. Можно делать как угодно. Руководствоваться надо здравым смыслом. Ни один подход не работает везде.
Смысл использования фабрик и репозиториев в создании переносимого ядра приложения. Под переносимостью я тут понимаю способность написанного кода работать в разных окружениях — в вебе, в консоли, на десктопе, в службе на сервере. Детали реализации будут меняться, а бизнес-логика (код в ядре) будет оставаться прежним.
Здравствуйте, Vladek, Вы писали:
AVK>>Почему главные объекты предметной области обязательно агрегаты? V>Потому что, как правило, они моделируют некую относительно сложную сущность, состоящую из отдельных малых частей. Следовательно, агрегат.
А как быть с SRP?
AVK>>Зачем что то сперва доставать для внесения изменений? V>Я использую фабрики или оператор new, чтобы создать объект, которого раньше не существовало, и репозитории, чтобы достать объект, который уже где-то есть.
Вопрос был в том зачем это делать, если нужно внести изменения.
V> Это просто, это логично, это работает.
Считывать данные из БД чтобы их поменять? Нет, это совсем не логично и не просто.
V>>> Агрегаты сами себя умеют записывать в бд или куда им это надо. AVK>>Поштучно? V>Поштучно, с помощью репозитория пучком если именно так надо.
Подожди. Вот есть у тебя предикат, по которому, к примеру, надо изменить определенное поле. Т.е. нужны, в итоге, примерно такие запросы:
UPDATE Log SET DeletedOn = GETDATE() WHERE CreatedOn < DATEADD(-30, D, GETDATE())
...
UPDATE Tasks SET CaptureCount = CaptureCount + 3 WHERE Type = 4
Как это выразить в твоей модели?
AVK>>А если вдруг транзакция и на чтение и на запись вместе? V>Радуюсь, что мне никогда такое не встречалось.
Ээээ, я вот даже не знаю как прокомментировать. Пожалуй, тогда вообще воздержусь.
V> Нужен конкретный пример, где действия пользователя нельзя чётко разделить на запросы и команды.
Списываем баланс с одного счета, начисляем на другой. Последовательность действий: считать операции по исходному счету, просуммировать, получить баланс, вычислить корректирующую сумму, записать сумму с минусом на старый счет, с плюсом на новый. Все, разумеется, в одной транзакции. Вычисление баланса, разумеется, нужно более чем в одном месте.
Достаточно конкретный пример или еще что то уточнить нужно?
AVK>>Т.е. на каждую операцию дравствую полная тележка boilerplate кода? AVK>>Т.е. на каждый запрос заводим как минимум один метод? V>Стереотипный код писать я тоже не люблю.
Зачем тогда советуешь? Особенно это касается рассказов про CQRS, чтоб Фаулеру икалось.
V> Мне важнее другое — вот папка, где лежит код работы с ORM. Я могу его прочитать весь сразу, я могу его поменять как угодно, и мне не надо особо трогать файлы и классы за пределами этой папки.
Зато появляется стадо контрактов, которое таки влияет на остальной проект. А, учитывая что все нормальные ORM умеют работать с POCO, получается куча развлечений по написанию кода без какого либо смыслового наполнения. Зато красивого и простого, да.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, Vladek, Вы писали:
AVK>>>Почему главные объекты предметной области обязательно агрегаты? V>>Потому что, как правило, они моделируют некую относительно сложную сущность, состоящую из отдельных малых частей. Следовательно, агрегат.
AVK>А как быть с SRP?
Думать о SRP когда какой-то код уже написан. Красивое и стройное решение сразу не напишешь, надо быть готовым что код будет так или иначе писаться несколько раз.
AVK>>>Зачем что то сперва доставать для внесения изменений? V>>Я использую фабрики или оператор new, чтобы создать объект, которого раньше не существовало, и репозитории, чтобы достать объект, который уже где-то есть. AVK>Вопрос был в том зачем это делать, если нужно внести изменения.
Чтобы модель предметной области проявила своё поведение. Другими словами, чтобы отработала бизнес-логика. Если вся суть работы приложения заключается в изменение таблиц БД, то это просто накопление данных и тогда действительно модель предметной области не нужна. если бизнес-логика есть, я оформляю её в виде модели предметной области.
V>> Это просто, это логично, это работает.
AVK>Считывать данные из БД чтобы их поменять? Нет, это совсем не логично и не просто.
Пробуждаем модель предметной области (загружаем её состояние в оперативную память из бд), отрабатываем нужное поведение (записываем изменённое состояние из оперативной памяти в бд).
V>>>> Агрегаты сами себя умеют записывать в бд или куда им это надо. AVK>>>Поштучно? V>>Поштучно, с помощью репозитория пучком если именно так надо.
AVK>Подожди. Вот есть у тебя предикат, по которому, к примеру, надо изменить определенное поле. Т.е. нужны, в итоге, примерно такие запросы: AVK>
AVK>UPDATE Log SET DeletedOn = GETDATE() WHERE CreatedOn < DATEADD(-30, D, GETDATE())
AVK>...
AVK>UPDATE Tasks SET CaptureCount = CaptureCount + 3 WHERE Type = 4
AVK>
AVK>Как это выразить в твоей модели?
Что мы здесь моделируем, какая бизнес-задача? Ты рассуждаешь об изменении бд — ну так и отобрази это в коде — выполни нужные запросы к серверу бд и дело с концом. Чем проще, тем лучше. Я повторюсь, если решение задачи выглядит как набор скриптов для бд — надо написать скрипты и больше с задачей не возиться.
AVK>>>А если вдруг транзакция и на чтение и на запись вместе? V>>Радуюсь, что мне никогда такое не встречалось.
AVK>Ээээ, я вот даже не знаю как прокомментировать. Пожалуй, тогда вообще воздержусь.
V>> Нужен конкретный пример, где действия пользователя нельзя чётко разделить на запросы и команды.
AVK>Списываем баланс с одного счета, начисляем на другой. Последовательность действий: считать операции по исходному счету, просуммировать, получить баланс, вычислить корректирующую сумму, записать сумму с минусом на старый счет, с плюсом на новый. Все, разумеется, в одной транзакции. Вычисление баланса, разумеется, нужно более чем в одном месте. AVK>Достаточно конкретный пример или еще что то уточнить нужно?
Уточнять можно бесконечно. Счета могут быть в разных банках, записи в разных хранилищах. Блокируются ли операции по счёту пока выполняется перевод или нет. И так далее. Задача модели предметной области всё это явно выразить в коде, только и всего. Универсального подхода нет, я вообще рассуждаю о обычном ООП.
public void Transfer(Account other, decimal amount)
{
// вот у нас два объекта Account: this и other.
// тут мы запускаем транзакцию
// считываем историю операций
// считаем баланс
// добавляем новые операции
// завершаем транзакцию
}
Чего тут такого сакрального, что перечёркивает мои потуги к решению задачи на нет?
AVK>>>Т.е. на каждую операцию дравствую полная тележка boilerplate кода? AVK>>>Т.е. на каждый запрос заводим как минимум один метод? V>>Стереотипный код писать я тоже не люблю.
AVK>Зачем тогда советуешь? Особенно это касается рассказов про CQRS, чтоб Фаулеру икалось.
Советуй другое.
V>> Мне важнее другое — вот папка, где лежит код работы с ORM. Я могу его прочитать весь сразу, я могу его поменять как угодно, и мне не надо особо трогать файлы и классы за пределами этой папки.
AVK>Зато появляется стадо контрактов, которое таки влияет на остальной проект. А, учитывая что все нормальные ORM умеют работать с POCO, получается куча развлечений по написанию кода без какого либо смыслового наполнения. Зато красивого и простого, да.
Я не использую POCO объекты для ORM в качестве модели предметной области. Раньше я так делал и это был говно-код, который очень быстро начинал вонять по мере развития проекта. Больше я такой говно-код не пишу и другим не советую.
Модель предметной области для реализации бизнес-логики и простые структуры данных для отображения пользователю — вот такой код мне удобнее всего (и получается короче).
Здравствуйте, gandjustas, Вы писали:
G>Грабля номер раз: проекция. Если твой метод возвращает IEnumerable<T>, где T — сущность замапленная на таблицу, то твой код — тормозное говно.
А где там IEnumerable<T>?
Первый предлагаемый автором способ возвращает Expression<Func<T, bool>>, второй -- IQueryable<Comment>
Здравствуйте, Shmj, Вы писали:
S>А где там IEnumerable<T>? S>Первый предлагаемый автором способ возвращает Expression<Func<T, bool>>, второй -- IQueryable<Comment>
Это я про repository. Хотя в статье он упоминается как анти-паттрен.
Здравствуйте, Sinclair, Вы писали:
S>Профайлер, есессно, никто не подключает — к чему его подключать? Мы и так знаем, чем занимается система — поднимаем объекты в память, подправляем, записываем обратно. И так 20 миллионов раз. S>Пользователи — не жалуются. "Чо ж вы хотели — 30 гигов, как-никак!" Сравнить им, бедняжкам, не с чем — переезд на другую систему будет стоить полгода и пару миллионов долларов. Не то, чем хочется заняться в выходные. S>В итоге инженеры, поохав про то, что "ну никак невозможно! Мы там процентов 10 сможем отжать, и всё!" выкидывают нахрен ORM из апгрейд-процесса, аккуратно переписывают всё на SQL, и апгрейд укладывается в 1 час 40 минут. S>Конечно, после такого приходится пить успокоительное. Ну, чтоб в табло не заехать тем, кто уверял про "никак невозможно".
Какая прохладная история. Я-то думал, что "пишу всё на хранимках с 91-го — хорошо себя чуствую" уже перевелись, а подиж-ты, жива старая гвардия!
Если вкратце — то всё это хорошо для тех, кто сложнее northwind баз не пользовал. А если выглянуть за пределы уютненького, то внезапно(!) окажется, что в процессе "подправляем" еще нужны данные из друго источника, поднимаются какие-то тонны правил в WF и попутно вызываются пара библиотек, которые в движок БД не завезли. Упс.
Поэтому разговор с вменяемыми базовиками на эту тему обычно напоминает старый анекдот "ты сильный, ты сможешь — не, я умный, я даже не возьмусь". С невменяемыми примерно то же самое, но они сначала берут месяц-два на "аккуратно переписывают всё на SQL", потом много "пить успокоительное" и так трогательно заглядывая в глаза "а у вас нет более другой ORM, вот такой же, но быстнее? Смотрите, уже быстрее, на целых 10%! Вот видите, есть же, а говорили, что не сможете!.. А, ну да.".
Собсна весь конфликт вращается вокруг этого самого: люди, у которых вся "логика" помещается в "update where" не понимают проблем тех, у которых перед update запускается ядерный реактор.
Вобщем это два разных Шапиро и вместе им не сойтись.
Но вообще в треде на тему делать ли обёртки поверх EF богатая идея "выкидывают нахрен ORM и аккуратно переписывают всё на SQL" — это успех однозначно.
Здравствуйте, Vladek, Вы писали:
AVK>>А как быть с SRP? V>Думать о SRP когда какой-то код уже написан.
А пока не написан, то фиг ли думать, трясти надо?
V> Красивое и стройное решение сразу не напишешь, надо быть готовым что код будет так или иначе писаться несколько раз.
Вопрос в другом — зачем писать заведомо кривое при любых раскладах решение? И, кстати, чем активнее ты кладешь болт на SRP, тем труднее потом это все будет переписывать.
V>>>Я использую фабрики или оператор new, чтобы создать объект, которого раньше не существовало, и репозитории, чтобы достать объект, который уже где-то есть. AVK>>Вопрос был в том зачем это делать, если нужно внести изменения. V>Чтобы модель предметной области проявила своё поведение.
Чего?
V> Другими словами, чтобы отработала бизнес-логика.
Зачем для отрабатывания бизнес-логики читать ненужные данные?
V> Если вся суть работы приложения заключается в изменение таблиц БД V>, то это просто накопление данных и тогда действительно модель предметной области не нужна. если бизнес-логика есть, я оформляю её в виде модели предметной области.
К чему эта абстракция? Вопрос был вполне конкретным — зачем что то читать, если задача стоит записать?
AVK>>Считывать данные из БД чтобы их поменять? Нет, это совсем не логично и не просто. V>Пробуждаем модель предметной области
Ты перешел к описанию решения. А я спрашивал про задачу.
AVK>>Подожди. Вот есть у тебя предикат, по которому, к примеру, надо изменить определенное поле. Т.е. нужны, в итоге, примерно такие запросы: AVK>>
AVK>>UPDATE Log SET DeletedOn = GETDATE() WHERE CreatedOn < DATEADD(-30, D, GETDATE())
AVK>>...
AVK>>UPDATE Tasks SET CaptureCount = CaptureCount + 3 WHERE Type = 4
AVK>>
AVK>>Как это выразить в твоей модели? V>Что мы здесь моделируем, какая бизнес-задача?
Да вроде все тривиально. В первом случае пометить записи в логе старше 30 дней удаленными. Во втором — увеличить количество захватов задач на единицу для типа 4. Никакой специфики БД, все в терминах предметной области.
Так будет решение, или опять витание в абстракциях без ответа на конкретный вопрос?
V> Ты рассуждаешь об изменении бд — ну так и отобрази это в коде — выполни нужные запросы к серверу бд и дело с концом.
Я спрашиваю как предполагается это все делать у тебя.
AVK>>Списываем баланс с одного счета, начисляем на другой. Последовательность действий: считать операции по исходному счету, просуммировать, получить баланс, вычислить корректирующую сумму, записать сумму с минусом на старый счет, с плюсом на новый. Все, разумеется, в одной транзакции. Вычисление баланса, разумеется, нужно более чем в одном месте. AVK>>Достаточно конкретный пример или еще что то уточнить нужно? V>Уточнять можно бесконечно.
Но ответа все равно не будет?
V>>>Стереотипный код писать я тоже не люблю. AVK>>Зачем тогда советуешь? Особенно это касается рассказов про CQRS, чтоб Фаулеру икалось. V>Советуй другое.
Т.е. ответа опять нет.
AVK>>Зато появляется стадо контрактов, которое таки влияет на остальной проект. А, учитывая что все нормальные ORM умеют работать с POCO, получается куча развлечений по написанию кода без какого либо смыслового наполнения. Зато красивого и простого, да. V>Я не использую POCO объекты для ORM в качестве модели предметной области. Раньше я так делал и это был говно-код
Почму?
V>который очень быстро начинал вонять по мере развития проекта.
Конкретнее.
V>Модель предметной области для реализации бизнес-логики и простые структуры данных для отображения пользователю — вот такой код мне удобнее всего (и получается короче).
Приведи пример. Т.е. был такой говнокод на РОСО, потом ты его переписал и он стал вот таким вот прекрасным.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, Vladek, Вы писали:
AVK>>>А как быть с SRP? V>>Думать о SRP когда какой-то код уже написан.
AVK>А пока не написан, то фиг ли думать, трясти надо?
V>> Красивое и стройное решение сразу не напишешь, надо быть готовым что код будет так или иначе писаться несколько раз.
AVK>Вопрос в другом — зачем писать заведомо кривое при любых раскладах решение? И, кстати, чем активнее ты кладешь болт на SRP, тем труднее потом это все будет переписывать.
V>>>>Я использую фабрики или оператор new, чтобы создать объект, которого раньше не существовало, и репозитории, чтобы достать объект, который уже где-то есть. AVK>>>Вопрос был в том зачем это делать, если нужно внести изменения. V>>Чтобы модель предметной области проявила своё поведение.
AVK>Чего?
V>> Другими словами, чтобы отработала бизнес-логика.
AVK>Зачем для отрабатывания бизнес-логики читать ненужные данные?
Затем, что сколь-нибудь нетривиальная программа имеет своё внутреннее состояние, накопленное за время жизни программы или загруженное откуда-нибудь. Вот это я и понимаю под пробуждением модели предметной области — привести программу в состояние, адекватное выполнению команды на изменение. Что может привести к чтению данных из бд перед записью данных обратно в бд.
Допустим, мы только что запустили программу, которая хранит свои данные в бд — первым делом она что-то оттуда прочитает.
V>> Если вся суть работы приложения заключается в изменение таблиц БД V>>, то это просто накопление данных и тогда действительно модель предметной области не нужна. если бизнес-логика есть, я оформляю её в виде модели предметной области.
AVK>К чему эта абстракция? Вопрос был вполне конкретным — зачем что то читать, если задача стоит записать?
Максимально конкретно — пришёл запрос от пользователя что-то записать в бд. Надо проверить права пользователя, вдруг у него прав нет что-то менять. Права пользователя прописаны в той же бд. Будем слепо выполнять запрос пользователя или проверим его права сначала? Проверим, то есть загрузим профиль пользователя из бд, совершим операцию чтения, и уже решим выполнять запись или нет. Окей, у нас профиль пользователя в памяти висит, не надо ничего читать из бд. Выполняем проверку на права и решаем выполнять запись или нет.
Я всегда предполагаю, что в памяти у нас ничего нет, состояние программы перед выполнением операции надо полностью реконструировать. В самом первом посте этой бесполезной ветки я ответил на вопрос как я это делаю. Я отвечаю на вопрос откуда берётся профиль пользователя в памяти, как это оформляется в коде. Как часто программист будет читать профиль пользователя из бд — решает он сам, руководствуясь здравым смыслом.
Вот зачем что-то читать, перед тем как записать. Я ответил как создаются корневые объекты модели предметной области. Простые конструкторы, фабрики и репозитории. Ещё раз, создаются они обычно если их до этого в памяти не было. Как? Конструкторы, фабрики, репозитории. Когда — решает программист.
AVK>>>Считывать данные из БД чтобы их поменять? Нет, это совсем не логично и не просто. V>>Пробуждаем модель предметной области
AVK>Ты перешел к описанию решения. А я спрашивал про задачу.
AVK>>>Подожди. Вот есть у тебя предикат, по которому, к примеру, надо изменить определенное поле. Т.е. нужны, в итоге, примерно такие запросы: AVK>>>
AVK>>>UPDATE Log SET DeletedOn = GETDATE() WHERE CreatedOn < DATEADD(-30, D, GETDATE())
AVK>>>...
AVK>>>UPDATE Tasks SET CaptureCount = CaptureCount + 3 WHERE Type = 4
AVK>>>
AVK>>>Как это выразить в твоей модели? V>>Что мы здесь моделируем, какая бизнес-задача?
AVK>Да вроде все тривиально. В первом случае пометить записи в логе старше 30 дней удаленными. Во втором — увеличить количество захватов задач на единицу для типа 4. Никакой специфики БД, все в терминах предметной области. AVK>Так будет решение, или опять витание в абстракциях без ответа на конкретный вопрос?
V>> Ты рассуждаешь об изменении бд — ну так и отобрази это в коде — выполни нужные запросы к серверу бд и дело с концом.
AVK>Я спрашиваю как предполагается это все делать у тебя.
Создаём объект лога через фабрику или репозиторий. Вызываем у него метод Purge, который выполнит этот запрос UPDATE. Лог обновляет сам себя.
Модель предметной области должна отвечать на вопрос — зачем вызывать метод Purge. Тривиальны конечные операции. Условия по которым они должны быть выполнены тут никакие не указаны. Что тут моделировать? Помечаем старые записи в логе — зачем и почему? Модель предметной области отвечает на этот вопрос. Наступило какое-то событие, время подошло — вот уже можно что-то моделировать.
Чёрный ящик выполнил запрос UPDATE Log... Что должно быть в чёрном ящике?
С Tasks то же самое.
AVK>>>Списываем баланс с одного счета, начисляем на другой. Последовательность действий: считать операции по исходному счету, просуммировать, получить баланс, вычислить корректирующую сумму, записать сумму с минусом на старый счет, с плюсом на новый. Все, разумеется, в одной транзакции. Вычисление баланса, разумеется, нужно более чем в одном месте. AVK>>>Достаточно конкретный пример или еще что то уточнить нужно? V>>Уточнять можно бесконечно.
AVK>Но ответа все равно не будет?
Я надеюсь кто-нибудь другой найдёт в моей писанине рациональное зерно.
V>>>>Стереотипный код писать я тоже не люблю. AVK>>>Зачем тогда советуешь? Особенно это касается рассказов про CQRS, чтоб Фаулеру икалось. V>>Советуй другое.
AVK>Т.е. ответа опять нет.
Тебе не нужны мои ответы. Ты прочитал мой первый пост в ветке, решил что это туфта — и написал свой пост. Но начал мутить воду наводящими вопросами. Лучше сразу писать что это всё туфта и по пунктам. Вдруг действительно туфта. Или показываешь какой-нибудь проект, свой или чужой, где репозитории и прочее приготовлены правильно. Это будет полезно хотя бы.
Здравствуйте, Vladek, Вы писали:
AVK>>Зачем для отрабатывания бизнес-логики читать ненужные данные? V>Затем, что сколь-нибудь нетривиальная программа имеет своё внутреннее состояние, накопленное за время жизни программы или загруженное откуда-нибудь
Спорно. Особенно весело с состоянием становится в кластерах. Прошли те времена, когда серверный софт работал на standalone серверах. Сейчас минимум пара машин просто для обеспечения минимальной надежности.
AVK>>К чему эта абстракция? Вопрос был вполне конкретным — зачем что то читать, если задача стоит записать? V>Максимально конкретно — пришёл запрос от пользователя что-то записать в бд. Надо проверить права пользователя, вдруг у него прав нет что-то менять. Права пользователя прописаны в той же бд. Будем слепо выполнять запрос пользователя или проверим его права сначала? Проверим, то есть загрузим профиль пользователя из бд, совершим операцию чтения, и уже решим выполнять запись или нет. Окей, у нас профиль пользователя в памяти висит, не надо ничего читать из бд. Выполняем проверку на права и решаем выполнять запись или нет.
При чем тут права? Ты говорил о том, что надо читать тот объект, который ты менять собрался.
V>>> Ты рассуждаешь об изменении бд — ну так и отобрази это в коде — выполни нужные запросы к серверу бд и дело с концом. AVK>>Я спрашиваю как предполагается это все делать у тебя. V>Создаём объект лога через фабрику или репозиторий.
Непонятно. Что за объект лога? Уже появились какие то объекты, привязанные не к записи, а к таблице целиком?
V>С Tasks то же самое.
Покажи псевдокод. Словами непонятно.
V>Я надеюсь кто-нибудь другой найдёт в моей писанине рациональное зерно.
Кто ж его найдет, если ты сам не можешь ответить на простые и прямо поставленные вопросы?
AVK>>Т.е. ответа опять нет. V>Тебе не нужны мои ответы.
Решил перейти на личности? Кончились аргументы? Не во мне причина того, что ты не можешь на вопросы ответить.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Облачныые БД все кластерные, бери да смотри. С>> И как это вообще делается? AVK>Что именно?
Мне непонятно, как БД, спроектированную для работы на одном сервере БД, растягивают на несколько серверов?
Насколько мне известно, существует ограниченное количество способов распределения нагрузки по БД:
1) Шардинг, партиционирование в общем виде — из некоего ключа можно вывести номер узла с БД, к которому следует обратиться с запросом. Как только запрос выходит за рамки одной таблицы, задача резко усложняется. Этот пункт применим ко всем нижележащим.
2) В мастер читают и пишут, всё это летит на реплику, если мастер помер — запросы переключаются на реплику. Не увеличивает скорость, повышает надёжность.
3) В мастер пишут, читают из одной или нескольких реплик. Повышает и скорость — частично, и надёжность.
4) Oracle RAC, работает непонятно как, стоит как самолёт.
5) Облако. Внезапно, в облаке обычные базы (не Oracle RAC) приобретают волшебную способность распределять нагрузку по узлам. Как они это делают, непонятно. Возможно и не делают, я не видел. Магия какая-то. Почему это стоит дешевле Oracle RAC, и нет ли тут каких недоговорённостей (а.к.а. хайпово-маркетинговая магия), тоже непонятно.
Здравствуйте, Слава, Вы писали:
С>1) Шардинг, партиционирование в общем виде — из некоего ключа можно вывести номер узла с БД, к которому следует обратиться с запросом. Как только запрос выходит за рамки одной таблицы, задача резко усложняется. Этот пункт применим ко всем нижележащим. С>2) В мастер читают и пишут, всё это летит на реплику, если мастер помер — запросы переключаются на реплику. Не увеличивает скорость, повышает надёжность. С>3) В мастер пишут, читают из одной или нескольких реплик. Повышает и скорость — частично, и надёжность. С>4) Oracle RAC, работает непонятно как, стоит как самолёт. С>5) Облако. Внезапно, в облаке обычные базы (не Oracle RAC) приобретают волшебную способность распределять нагрузку по узлам. Как они это делают, непонятно. Возможно и не делают, я не видел. Магия какая-то. Почему это стоит дешевле Oracle RAC, и нет ли тут каких недоговорённостей (а.к.а. хайпово-маркетинговая магия), тоже непонятно.
Ну вот ты сам на все и ответил. Пачка NoSQL БД, ажуровский сторадж и сиквер при явном задании используют шардинг. Сиквел, кроме того, может и геореплики делать автоматично, и вручную настроенную синхронизацию.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK>Зачем для отрабатывания бизнес-логики читать ненужные данные?
это могут быть примерно такие случаи: мы пишем логику по обновлению чего-то.
но перед сохранением изменений, должны отработать разные правила проверок. эти правила проверок и могут использовать другие данные (поля)
из обновляемой записи и даже из других записей. причём когда мы пишем нашу логику по обновлению, нам конечно не хочется думать ещё и
обо всей логике этих проверок и тем более о их реализации. и в общем случае (тк мы не знаем какие там правила и какие им данные из записи понадобятся)
делается что в объект представляющий запись поднимаются все поля записи.
если конечно, когда мы пишем эту логику по обновлению мы точно знаем что проверять дополнительно ничего не придётся
и важно выжать максимум быстродействия — то вариант с прямым update в базу (ну или аналогом через ORM) оптимален.
но если мы заранее точно не знаем, или проверки могут появиться потом — то для общего случая приходиться так делать, в ущерб
производительности, но зато с упрощение кода (за счет отсутсвия заморачивания о ручном выполнении проверок — и фактически дублирования их логики).
под "так делать" имею ввиду наиболее часто применяемый способ (на своей практике) — работать через Entity и уже в самом классе Entity и реализованы эти правила проверки перед сохранением изменений в БД, а также и может быть бизнес-логика при присвоении в поля.
Здравствуйте, MadHuman, Вы писали:
MH>делается что в объект представляющий запись поднимаются все поля записи. MH>если конечно, когда мы пишем эту логику по обновлению мы точно знаем что проверять дополнительно ничего не придётся
То есть, требуется что-то вроде row level security, а то и какого-то механизма правил, но эти правила не представлены в БД, а прикрутили их потом, в коде, и объем их только растёт. И кто-то это должен поддерживать, а то и развивать.
Лучше, конечно, вообще не работать с подобными проектами и в подобных конторах. Пусть оно умрёт.
Здравствуйте, Слава, Вы писали:
С>Здравствуйте, MadHuman, Вы писали:
MH>>делается что в объект представляющий запись поднимаются все поля записи. MH>>если конечно, когда мы пишем эту логику по обновлению мы точно знаем что проверять дополнительно ничего не придётся
С>То есть, требуется что-то вроде row level security, а то и какого-то механизма правил, но эти правила не представлены в БД, а прикрутили их потом, в коде, и объем их только растёт. И кто-то это должен поддерживать, а то и развивать.
да.
код проверок — на языке программирования, со всеми вытекающими плюсами.
минус только в сценариях массового обновления, когда реально надо много данных заапдейтить. но к счастью обычно (на моей практике) кол-во таких сценариев относительно немного. но когда прижмет, что делать ведь ясно.
а вы предлагаете всё в базе на триггерах реализовать?
Здравствуйте, MadHuman, Вы писали:
MH>код проверок — на языке программирования, со всеми вытекающими плюсами.
А если проверка затрагивает большой объем данных? Все равно все вычитываем? Или можно уже что то в БД оставить, и вытаскивать запросом только результат?
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, MadHuman, Вы писали:
AVK>>Зачем для отрабатывания бизнес-логики читать ненужные данные? MH>это могут быть примерно такие случаи: мы пишем логику по обновлению чего-то. MH>но перед сохранением изменений, должны отработать разные правила проверок. эти правила проверок и могут использовать другие данные (поля) MH>из обновляемой записи и даже из других записей. причём когда мы пишем нашу логику по обновлению, нам конечно не хочется думать ещё и MH>обо всей логике этих проверок и тем более о их реализации. и в общем случае (тк мы не знаем какие там правила и какие им данные из записи понадобятся) MH>делается что в объект представляющий запись поднимаются все поля записи.
Давай без абстракций. Вот свежий пример из одного из реальных продуктов (ненужные подробности убраны):
var packageTemplate =
await db
.PackageTemplates
.Where(pt =>
pt.Type == PackageType.Subsription
&& pt.DeletedDate == null
&& pt.Tag == activationInfo.Edition)
.Select(pt => new { pt.Id, pt.PagesCount })
.SingleOrDefaultAsync();
HttpServiceCode.AssertState(
packageTemplate != null,
"PackageTemplateNotFound",
false,
$"Internal error: package template for {activationInfo.Edition} subscription not found.");
var actQuery =
db
.Subscriptions
.Where(s => s.SerialNumber == activationInfo.SerialNumber);
if (activationInfo.Limit > 1)
HttpServiceCode.AssertArgument(
!await actQuery.AnyAsync(s => s.UserId == userId),
nameof(activationInfo.SerialNumber),
"SerialNumberAlreadyUsedForUser",
true);
HttpServiceCode.AssertArgument(
await actQuery.CountAsync() < activationInfo.Limit,
nameof(activationInfo.SerialNumber),
activationInfo.Limit == 1
? "SerialNumberAlreadyUsed"
: "SerialNumberActivationsExceeded",
true);
db
.BalanceLogs
.Insert(() =>
new BalanceLog
{
Id = Guid.NewGuid(),
UserId = userId,
ClientId = clientId,
ObjectType = ObjectType.PackageTemplate,
ObjectId = packageTemplate.Id,
OperationType = OperationType.SubscriptionActivate,
PagesCount = packageTemplate.PagesCount.GetValueOrDefault(),
EndDate = activationInfo.ExpirationDate,
CreatedDate = DateTime.UtcNow
});
var subsId = Guid.NewGuid();
db
.Subscriptions
.Insert(() =>
new FRSubscription
{
Id = subsId,
CreatedOn = DateTime.UtcNow,
SerialNumber = activationInfo.SerialNumber,
UserId = userId
});
Вот здесь имеют место быть три проверки, потом две записи. Но нигде ничего не вычитывается из БД сверх скалярных данных в минимально требуемом объеме. Как это будет выглядеть в пропагандируемом тут подходе? Можно псевдокод.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
AVK>Давай без абстракций. Вот свежий пример из одного из реальных продуктов (ненужные подробности убраны): AVK> ..... skip ..... AVK>Вот здесь имеют место быть три проверки, потом две записи. Но нигде ничего не вычитывается из БД сверх скалярных данных в минимально требуемом объеме. Как это будет выглядеть в пропагандируемом тут подходе? Можно псевдокод.
ну во первых, я не пропагандирую какой-то конкретный подход как единственный тру, я за справедливость)
в твоем примере я невижу необходимости что-то менять. если проверки именно часть бизнес-процедуры, то таким образом ок.
но может быть другой пример. вот один из сценариев — выгружаются данные из таблицы в ексель (нужные колонки).
затем юзеры что-то в них правят и затем загружается обратно.
//процедура импорта
//пропущены всякие подготовительные действияvar updatedFields = ... //массив имен обновляемых полейforeach(var r in importedRows){
var record=db.Get(r["id"]);
foreach(var fname in updatedFields){
record[fname] = r[fname];
}
}
db.SaveChanges(); //тут и сработают предусмотренные для изменённых записей проверки
//конец процедуры
//проверки в классе Order, вызываются перед сохранением изменений (в недрах SaveChanges).
//Order может меняться: при сохранении изменений из ЮИ, при сохранении изменений от апи, при импорте изменений из файла, вариантов ЮИ-форм может быть много.
//и во всех этих случаях перед тем как сохранить надо проверить валидность нового состояния сущности.public override void Validate(){
//если заказ подтверждён - то должны быть заполнены поля номер, дата и приатачены все необходимые документыif (Status == OrderStatus.Accepted){
if (Number == null)
//для упрощения кода в примере - генерится исключение. в реальности возвращается ValidationResult с подробностями что не так. throw new UserError("Не заполнен номер");
if (Date == null)
throw new UserError("Не заполнена дата");
//и тд
}
//и тут ещё что-нибудь дохрена и не такого простого как выше, код логики может быть довольно сложный.
}
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, MadHuman, Вы писали:
MH>>код проверок — на языке программирования, со всеми вытекающими плюсами.
AVK>А если проверка затрагивает большой объем данных? Все равно все вычитываем? Или можно уже что то в БД оставить, и вытаскивать запросом только результат?
вопрос риторический? конечно в таком случае лучше запросом.
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, Au, Вы писали:
Au>>Этот код покрыт юнит тестами?
AVK>Да.
Au>> Можно их посмотреть (без ненужных подробностей разумеется)?
AVK> А зачем?
Ты привел некоторое решение, хочется видеть насколько оно эффективно с точки зрения полного цикла разработки/поддержки (то есть с тестами).
AVK> Там ничего удивительного нет, прогоняются юзкейсы основные. И без подробностей все равно пользы от них не будет.
Ничего удивительного и не нужно, хочется увидеть как тестируется твое решение. Покажи 1 тест, например который проверяет что ассерт SerialNumberAlreadyUsedForUser срабатывает при нужных условиях.
Здравствуйте, Au, Вы писали:
Au>А зачем нужна транзакция для юнит теста? Или используется реальная тестовая БД?
Используется реальная тестовая БД. Транзакция ее гарантированно подчищает при любом сбое. Но есть и другие варианты. Здесь такой выбран, потому что серийники генерируются и проверяются сторонней системой, и лень было докручивать клиента для этого.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали:
AVK>А новая гвардия что делает? Пишет мегарешение, которое кушает 5 килобаксов в месяц за облако, выполняя при этом малую долю работы похожей системы, тратящей 1.5К в месяц? При том что и у второй системы не все идеально и с перфомансом, и с лишним бойлерплейтом. AVK>Если что — это как раз реальный пример применения модных технологий и CQRS.
Тут вопрос спорный. Разница в затратах на облако, насколько я понял, где-то $3500. Это меньше нижней з/п одного программиста в США и где-то 1.5 з/п хороших программистов в МСК. С точки хрения бизнеса возникает резонный вопрос: Сколько нужно программистов и какой квалификации (и, соответственно, с какой з/п) чтобы поддерживать и развивать оба этих проекта?
P.S. Я не оправдываю Repository, т.к. пытался использовать его в реальных проектах и быстро пришел к тем же заключениям, которые были описаны выше (короче отказался). Я говорю, что указывать просто затраты на облако с точки зрения бизнеса некорректно.
Здравствуйте, AngeL B., Вы писали:
AB>Тут вопрос спорный.
Зато результаты совершенно бесспорные.
AB> Разница в затратах на облако, насколько я понял, где-то $3500. Это меньше нижней з/п одного программиста в США и где-то 1.5 з/п хороших программистов в МСК. С точки хрения бизнеса возникает резонный вопрос: Сколько нужно программистов и какой квалификации (и, соответственно, с какой з/п) чтобы поддерживать и развивать оба этих проекта?
О, а тут все еще смешнее. Первый проект большую часть его истории разрабатывал один человек. А второй — целая немаленькая команда. И закончилась его печальная судьба в том числе потому что новые люди очень много времени тратили на въезжание.
AB>P.S. Я не оправдываю Repository
Дело не в Repository, дело в основном в CQRS. И в том что смелые парни решили зачем то написать асинхронный велосипед с распределенными данными, при этом явно не имея в таком даже базового опыта.
AB>, т.к. пытался использовать его в реальных проектах и быстро пришел к тем же заключениям, которые были описаны выше (короче отказался). Я говорю, что указывать просто затраты на облако с точки зрения бизнеса некорректно.
Сколько оговорок. Но видишь ли какое дело — когда стадо виртуалок само себя нагружает работой и имитирует бурную деятельность, при этом чтобы понять что там вообще происходит понадобились очень хорошие спецы и много времени — тут все отсылки на зарплаты программистов не канают.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AngeL B., Вы писали:
AB>P.S. Я не оправдываю Repository, т.к. пытался использовать его в реальных проектах и быстро пришел к тем же заключениям, которые были описаны выше (короче отказался). Я говорю, что указывать просто затраты на облако с точки зрения бизнеса некорректно.
Лично мне проще пользоваться запросами, чем вот этой необыкновенной ерундой с ORM и развесистой иерархией репозов, которые сами не делают ничего. Вообще, обилие разного рода SQL-бабок, т.е. женщин средних лет, которые занимаются только БД и которым платят не столь много, как бы говорит нам, что уметь пользоваться запросами — это несложно.