Pull to refresh

Comments 110

Что делать если к этой базе должно подключаться стороннее приложение?
— например, риэлтеры отмечают сделки в этой программулине а для сайта эти же данные должен брать пхп-скрипт. Он должен лезть в базу а значит любые изменения в этой программулине (а значит и базы) ведут в дополнительной правке стороннего приложения.

Что делать если структура данных изменилась?
— например привязку к метро отменили и привязывают к обслуживающему дом ЖЭКу. Как конвертировать имеющиеся данные?

Что делать при регулярно-меняющейся отчётности? Например общую сумму только по комнатам (roomOffer) только агента Сидорова? На SQL это один запрос выполняемый за секунду на практически любом объёме данных.

Что делать если вводятся дополнительные проверки, например адрес должен быть привязан к КЛАДР? Переделать эту программулину и добавить проверку, перелопатить данные и проверить, переделать все другие приложения работающие с этими данными?

Вижу только быструю разработку.

С поддержкой, разделением данных между приложениями и производительностью одна беда.
Что делать если к этой базе должно подключаться стороннее приложение?
Понятно же — реализовать API для работы с БД (все сторонние клиенты работают только с API, а не с БД напрямую). Сделать это можно, например, создав Web Service (стандарт которого — кросс-платформен).
тогда заявление о быстрой разработке неверно. Если ещё и АПИ надо делать.
Такое апи делается за минуты
Такому апи цена — грош.
Такой аргумент — не дороже
API может быть сгенерировано автоматически
от разработчика задача только описать класс сущности
Вы так сказали будто если не использовать ORM АПИ делать не нужно
если бизнес-логика определяется структурой базы данных (да-да, стрелочки между табличками) то никакого API не нужно. Все приложения обращаются к SQL-серверу и он сам следит за соблюдением правил, оптимизацией и разделением прав доступа.

Фактически, то что описано в статье это отбросить всё что предоставляет SQL-сервер и городить своё, родное.
«если бизнес-логика определяется структурой базы данных (да-да, стрелочки между табличками)»
… то это не бизнес-логика, а смех.

В остальном же, то, о чем вы пишете — это классические аргументы за перенос бизнес-логики из прикладного слоя в БД и за использование БД в качестве точки интеграции. Been there, seen that. Так делать по первости удобно, но есть проблема: я не знаю ни одной современной РСУБД, которая была бы настолько уже удобна в разработки и последующей поддержке, что и современные же языки программирования.

Компиляция и проверки периода компиляции? Нихьт.
Отслеживание зависимостей? Нихьт.
Рефакторинг? Нихьт.
Управление версиями? Нихьт.

PS SQL-сервер сам следит за разделением прав доступа? Были, ели. Для этого каждый пользователь лезет в базу под своим логином. Прощайте, масштабируемые фермы.
«если бизнес-логика определяется структурой базы данных (да-да, стрелочки между табличками)»
… то это не бизнес-логика, а смех.


бизнес-логика в базе позволяет решить проблемы описанные в первом комментарии к статье. Да, для этого обязательно понимание что такое «база данных».
минимально сложная бизнес логика требует написания кода
ваш вариант — писать его в процедурах
мой — в классах/методах

для валидации кода нужно будет выполнить выполнить этот код для каждой записи.

так чем же процедура удобнее метода?
в большинстве случаев структура базы данных уже является описанием бизнес-логики. Без всяких процедур. SQL-сервер не запускает валидацию для каждой записи, он немного иначе работает (за подробностями в соответствующую литературу).
«бизнес-логика в базе позволяет решить проблемы описанные в первом комментарии к статье»
Любую задачу можно решить более чем одним способом. Можно — переносом всего в СУБД. Вопрос цены.

Все аргументы против этого уже пережеваны неоднократно.

Ну и пройдемся еще раз по вашим вопросам:
«Что делать если к этой базе должно подключаться стороннее приложение?
— например, риэлтеры отмечают сделки в этой программулине а для сайта эти же данные должен брать пхп-скрипт. Он должен лезть в базу а значит любые изменения в этой программулине (а значит и базы) ведут в дополнительной правке стороннего приложения.»
Видим недостаточную изоляцию компонентов. Для каждого приложения, работающего с данными (данными! не БД!) должен быть зафиксирован свой интерфейс, через которое это приложение и работает. Этот интерфейс выступает необходимым уровнем абстракции.

«Что делать если структура данных изменилась?
— например привязку к метро отменили и привязывают к обслуживающему дом ЖЭКу. Как конвертировать имеющиеся данные?»
Миграционный скрипт. Никаких отличий от работы с СУБД напрямую.

«Что делать при регулярно-меняющейся отчётности? Например общую сумму только по комнатам (roomOffer) только агента Сидорова? На SQL это один запрос выполняемый за секунду на практически любом объёме данных.»
На правильном ORM он выполняется за столько же. А вообще, при появлении значительных требований по отчетности, нужно делить OLAP и OLTP и гонять данные ETL-скриптами.

«Что делать если вводятся дополнительные проверки, например адрес должен быть привязан к КЛАДР?»
Где вводятся?

«Переделать эту программулину и добавить проверку, перелопатить данные и проверить, переделать все другие приложения работающие с этими данными?»
Переделать те места, где это критично. Собственно, смотри выше про уровень абстракции данных.
Разделения прав доступа в SQL сервере практически во всех случаях не будет хватать. Дальше безопасность. Только в очень редких случаях я бы доверил выполнение sql со стороны. Те же риэлтеры могут закинуть в базу инфицированный текст.
текст нельзя инфицировать

того что не разрешено в базу закинуть нельзя, SQL-сервер это гарантирует.
Речь шла о SQL иньекциях.
И это кстати тоже. Ведь если работу с базой переложить на плечи сторонних разработчиков они могут допустить различные уязвимости. А расхлебывать будем мы
Наоборот.

Если пользователю в базе, скажем, запрещено удалять историю продаж квартир то никакие SQL-команды не помогут ему это сделать.

В описанном случае мне достаточно подключиться к базе и я смогу выполнить любые команды т.к. никаких проверок там нет.
Ну что бы вы могли подключится к базе вы должны знать логин и пароль. А таким же успехом если вы будете знать доступ к ftp вы можете удалить все файлы сайта.
получается что в описанном случае сторонняя программа не может подключаться к базе (т.к. может испортить данные если знает логин/пароль а проверок в базе нет).

Нужно делать АПИ доступа в котором проверять права.
Нужно в нём реализовать все запросы которые могут понадобиться сторонним программам (например кому-то понадобится общая сумма по сделкам а этого нет в описанной программулине).
Также этот АПИ нужно поддерживать.

В случае использования бизнес-логики в БД все этой работы выполнять не нужно.
А в вашем случае вы выстраиваете защиту описывая ее в процедурах. Часто фильтрация входящих данных далеко не тривиальна. Ниже я привел пример про XSS. Часто такую фильтрацию выполняют библиотеки размером до 1000 строк. Представляю размер ваших процедур…
нужно понять что процедуры и безнес-логика в структуре базы данных это разные вещи.
А что такое бизнес-логика в структуре БД?
Бизнес-логика — в разработке информационных систем — совокупность правил, принципов, зависимостей поведения объектов предметной области (области человеческой деятельности, которую система поддерживает). Иначе можно сказать, что бизнес-логика — это реализация правил и ограничений автоматизируемых операций. Является синонимом термина «логика предметной области» (англ. domain logic).

вики

может быть описана как структура базы данных, для этого используется

Реляционная модель данных (РМД) — логическая модель данных, прикладная теория построения баз данных, которая является приложением к задачам обработки данных таких разделов математики как теории множеств и логика первого порядка.

вики
Т.е. модель предметной области есть часть бизнес логики? Я раньше понимал что бизнес логика, это операции, которые можно совершать в рамках каких либо процессов, над доменной моделью (моделью предметной области). Ну да ладно, видимо не верно понимал.

Но вы все таки не пишите ту часть логики, которая описывается не структурой БД, в хранимках. С этим очень трудно жить. Не вижу никаких плюсов в хранимках. Возможно использование хранимок, только в искусственно созданных обстоятельствах. Во всех остальных случаях, это необоснованные затраты.
понимаете, я в этом обсуждении уже несколько раз повторил что безнес-логика в структуре базы данных и процедуры это совершенно разные вещи.

Нужно ли мне ещё раз это повторять?
Извините, сюда писал.

Что такое «бизнес-логика в структуре БД»?
Хы, оказывается комментить ниже незя. :) :)
Как бы вы решили защиту от XSS? XSS я привел лишь потому что это первое что пришло в голову защита от которого не пишется в 1 строчку.
Также если для добавления записи нужно обязательно заполнить некоторые поля. Если поля не заполнены запись не добавляется. Скрипт что инициировал запрос должен знать об ошибке и что произошло. Не думаю что это просто сделать вашим способом.
В новой версии хабра обещают поднять максимальный уровень вложенности до 90
Ммм… Очень часто вижу как пихают бизнес-логику в БД. Не делайте этого. Вспомните великие слова: «Представьте, что код, который вы написали, будет сопровождать маньяк, который знает где вы живете».
«В описанном случае мне достаточно подключиться к базе и я смогу выполнить любые команды т.к. никаких проверок там нет.»
Угу. Вот только под каким пользователем вы это будете делать, если в базе их два: админ и «пользователь-под-которым-ходит-апп-сервер», и пароль от второго хранится так же свято, как и от первого?
SQL-инъекция это когда в текст запроса без проверки добавляют переменные введённые пользователем. Все знают что так делать нельзя, все знают что это решается стандартным cmd.Prepare(); или типа того.
И тут мы подходим к тому, что время затраченное на предотвращение подобного слихвой покрывают время на реализацию API, которое, как было замечено ранее, можно написать минут за 15. Более сложное АПИ подразумевает сложную бизнес логику, которую, если переносить все в БД, без процедур вы не реализуете.
так предотвращение не стоит ничего. Ни в сложных ни в простых программах. Есть правила, их надо выполнять:

Нужно пользоваться PreparedStatement.

И всё. Типа как нельзя хранить пароль на стикере приклеенном на мониторе, нельзя давать ключ от квартиры незнакомым людям.

Большинство ОРМ, кстати, это правило выполняет.
Хорошо, вот вам простой пример
У вас есть некая система, логика которой полностью вынесена в БД. Предположим что логика там банальная — просто CRUD, например для хранения какого-то контента. В таком случае худо бедно все будет хорошо.
В качестве СУБД вы желаете использовать MySQL/MSSQL/Oracle и тд.

К вашему проекту подключаются извне, напрямую к базе, что бы внести изменения. Тобиш вместо стандартного SOAP сервиса, который, в силу простоты логики, реализуется за 5 минут, программисту стороннего приложения придется потратить кучу времени что бы разобраться как со всем этим работать. В случае если бы вы потратили эти 5 минут на создание проекта в среде разработки, описание сущности (почти все фреймворки умеют генерить их из таблиц) и создание сервиса (по сути просто объявить методы, которые уже реализуют CRUD операции, помниться мне вижла хорошо это делала сама), то жить стало бы лучше всем. Программист стороннего приложения получает WSDLку, которая полностью реализует API на его языке программирования к вашему сервису. То бишь разницы нету писать на яве, objective-c или на php, все стандартизировано. Как минимум для разработчика стороннего приложения вы сделаете хорошо. Ну а вам возможность легко вносить изменения в логику без изменения интерфейса. Может быть вам придется организовать репликацию, шардинг или еще чего.

Как бы я не настаиваю на таком подходе, могут быть разные требования, но как по мне пихать всю логику в базу это не есть хорошо.
Я имел в виду XSS если текст с базы использоваться в вебе
Что делать если к этой базе должно подключаться стороннее приложение?
Предполагается, что доступ до приложения (данных) будет через сервис (XML/JSON и т.д.)

Что делать если структура данных изменилась?
1. Удаляем метро
2. Добавляем ЖЭК
3. Заполняем ЖЭК

Что делать при регулярно-меняющейся отчётности?
Мы используем ReportingServices, который потребляет данные через XML источник данных.
Конкретно для суммы по комнатам — один метод
public static decimal GetAgentsRoom(string name)
{
    return Query.All<RoomOffer>().Where(a => a.Creator.Name == name).Sum(a => a.Price);
}
Что делать если структура данных изменилась?
Ответ на этот вопрос характеризует гибкость написанного кода: если планируется переиспользование кода, то нужно не привязываться к конкретным классам, а работать с интерфейсами (тогда и способ хранения реализаций последних — не важен).
Вижу только быструю разработку.

Так вроде, как я понял, именно изменения вносить только в C# код проще и сами изменения короче, чем вносить их сперва в код, потом в пакет обновления БД.

Автор показал как раз как делать нельзя если строить n-tier приложение.
1. Сущности БД дальше слоя доступа к базе данных выходить не должны.
2. Верхние уровни(бизнес логика) должны работать с Data Transfer Objects например.
3. Генерировать формочки по моделям конечно легко и красиво, только очень быстро празник закачивается, как только нужно разнести приложение по разным сервисам, усложнить логику, добавить связь с внешними системами, все мы это уже проходили в Delphi, VB и даже .net с обилием code-behind обработчиков.
Теперь по вашим вопросам:
Что делать если к этой базе должно подключаться стороннее приложение? — SOA, REST или SOAP -based. Доступ к БД как сервис, только давать доступ к БД очень опасно, потому обычно над Data access layer строим business logic layer который делает валидацию и проверку на соотвествие бизнест требованиям запроса.
Что делать при регулярно-меняющейся отчётности? Например общую сумму только по комнатам (roomOffer) только агента Сидорова? На SQL это один запрос выполняемый за секунду на практически любом объёме данных.

Так уж и любой объём данных? Так не бывает и ниодна вменяемая компания обязательства на такой SLA не подпишет. Есть физические ограничения сколько вы можете сгенерить запросов и данных. Если захотите аналитику OLAP считать в real-time так вы у вас даже на ограниченных наборах даных легко вылезете за 1 сек. А зная примерно порядок и характер данных выбираете рецепт как удовлетворить заданое требование, может случится что у вас вообще не получится этого сделать на существующей архитектуре.
Что делать если вводятся дополнительные проверки, например адрес должен быть привязан к КЛАДР? Переделать эту программулину и добавить проверку, перелопатить данные и проверить, переделать все другие приложения работающие с этими данными? — Для этого business access layer и вводится. Если уж совсем часто меняться правила или разрабатывается движок ERP/CRM/etc. прикручивают какой-то rules-engine иногда ещё и с версионностю, чтобы проигрывать правила.
UFO just landed and posted this here
Добавить hint переименования. Hint — подсказка для ORM что же делать с БД при наличии различий между схемой построенной по классам и реальной схемой в SQL


Сегодня с утра съездил за запчастями, потом заправился, потом — в гараж договориться о покраске, потом в ГАИ… Представляешь, как бы я все это успел без машины?
Подробнее — на сайте анекдотов «Чортова Задница», посмотри, не пожалеешь: www.hellass.com/autohumor/669-without-car.html
— Когда Вы говорите, Иван Васильевич, впечатление такое, что вы бредите. (с) подробнее вики
Как я понял, хинт — это возможность автоматически сгенерировать скрипт обновления БД.
А можно просто руками написать такой скрипт, если я не хочу разбираться с хинтами?
я разочаровался в ОРМах. ИМХО удобней процедуры + мапер + тесты.
1. Не надо думать какой сгенерится sql в итоге.
2. Процедура — это sql без ограничений.
3. Процедуру всегда можно пофиксить на проде без выката всего приложения.
4. Уходит целый слой кода.
5. Код ферст? Неверю
1. Так у нас тоже не надо думать какой сгенерится sql
2. C# — это не только SQL, но и вся мощь ООП
3. Очень спорный момент, и может подпортить много нервов и админу и разработчику и не только
5. А вы попробуйте ;)
1. А как же печальные случаи когда в место 1 джоина делается 100 запросов по связанной сущности?
2. С# не включает в себя sql:) Бизнес приложения — это в большинстве просто редакторы БД, все крутится вокруг БД => бизнес логику лучше хранить в БД. ООП фишек лучше здесь избегать, нужен простой понятный код.
3. Да я понимаю что это грязный прием и с ним нужно очень аккуратно, но когда дырка в огне и люди бьют друг другу морды в очереди из-за завышенного уровня блокировки(реальный случай). Такая возможность приходится очень кстати.
4. Незнаю. Опять же придется думать какой же sql сгенерится. Зачем? Проще самому написать.
1. А как же печальные случаи когда в место 1 джоина делается 100 запросов по связанной сущности?

Это уже проблема ОРМ, в нашем случае делаются джойны всегда. Но можно и не делать, а можно вообще загрузить еще все связанные сущности сразу. Главное, чтоб был АПИ. Ну, и чтение доков никто не отменял пока :)
С# не включает в себя sql:) Бизнес приложения — это в большинстве просто редакторы БД, все крутится вокруг БД => бизнес логику лучше хранить в БД. ООП фишек лучше здесь избегать, нужен простой понятный код.

Смеялся, честно :)
4. Незнаю. Опять же придется думать какой же sql сгенерится. Зачем? Проще самому написать.

В 99% случаев, при использовании ОРМ, думать, какой код генерится — не надо. Когда надо — можно написать ручной SQL.
«Бизнес приложения — это в большинстве просто редакторы БД, все крутится вокруг БД => бизнес логику лучше хранить в БД»
Вы себе противоречите. Если бизнес-приложения — это «просто редакторы БД», то бизнес-логики там нет. А как только бизнес-логика появляется, держать ее надо там, где ее удобнее поддерживать.

«Опять же придется думать какой же sql сгенерится. Зачем?»
Вот именно: зачем вам об этом думать? Как только вы начинаете об этом думать, вы нарушаете данный вам уровень абстракции (и увеличиваете сложность вашей задачи). Или вы когда пишете SQL, думаете, в какие обращения к каким дорожкам жесткого диска это выльется?
1. Да слово «просто» я зря применил, потому что все на самом деле сложно:) Если логика крутится вокруг большего количества данных, зачем таскать их сквозь слои? Гораздо удобнее работать с данными на месте, т.е. в БД

2.Не люблю абстракций, люблю когда все предельно детерминированно — такие приложения поддерживать проще и они надежнее. Да я стараюсь думать о дисках когда пишу на sql и еще об оперативной памяти. БД как раз то самое место где стоит подумать о производительности.
«Гораздо удобнее работать с данными на месте, т.е. в БД»
Вот слово «удобнее» тут малоприменимо. Как я уже писал в одном из соседних комментариев, я еще не видел не одной БД, где было бы _удобно_ вести разработку сколь-нибудь сложной логики. Зависимости-рефакторинг-версионирование-?

«Не люблю абстракций, люблю когда все предельно детерминированно — такие приложения поддерживать проще и они надежнее.»
Значит, вам повезло, и ваше приложение достаточно просто, чтобы не возникало проблемы «бизнес-уровень не влезает в голову». _Только_ бизнес-уровень.
1. С рефакторингом помогают тесты. С версионированием ОРМ не помогает.
2. Только бизнес-уровнем думает аналитик. Мне приходится думать обо всем… бизнес -> sql это проще чем бизнес -> генератор sql -> sql
«С рефакторингом помогают тесты.»
Только вот для .net-кода есть еще инструменты, позволяющие это делать _до_ тестов. Получаем _три_ слоя поддержки вместо одного.
1. А как же печальные случаи когда в место 1 джоина делается 100 запросов по связанной сущности?
— EFProf или самописная приблуда которая логирует запросы, а дальше оптимизация LINQ запросов. Практика показывает, что большинство проблем решается и такое количество join возникает или изза плохой струтуры данных или некачественного написания запроса.
4. Незнаю. Опять же придется думать какой же sql сгенерится. Зачем? Проще самому написать. — write-once подход. Скрипты sql живут в одно месте, модели в другом, переименовали поле, изменили его тип, забыли предупредить товарища, а он потом будет мучатся 4 часа на площадке товарища выясняя почему приложение неожиданно падает.
>3. Процедуру всегда можно пофиксить на проде без выката всего приложения.
Простите, но за такое надо просто руки отрывать сразу. Мы как раз делали миграцию с одного такого приложения, его поддержка была просто АДОМ
>4. Уходит целый слой кода.
А структура БД у вас в астрале хранится?
При нашем подходе мы храним только классы(структуру) и данные(xml) — а дальше нам уже не важно, какая БД и что там. ОРМ сделает все за нас: приведет БД в актуальное состояние и даст к ней доступ.
>5. Код ферст? Неверю
У нас есть 10 готовых работающих проектов в продакшене, которые легко поддерживаются и изменяются. Вот этому я верю.
3. см выше
4. Ох как у вас все розово
5. Это круто, тут мне нечего сказать я не пробовал код ферст
Ну, выше я тоже ответил. А насчет «розово» — говорю, как есть, причем не в теории, а на практике.
«Процедура — это sql без ограничений.»
То есть надо знать два языка вместо одного.

«Процедуру всегда можно пофиксить на проде без выката всего приложения.»
За такие вещи убивают на месте без права оправдания. Почитайте Continuous Delivery.

«Уходит целый слой кода.»
Не «уходит», а «перемещается в БД». Где его автоматизированное тестирование затрудняется на два порядка.
То есть надо знать два языка вместо одного.

— именно. Поэтому даже вакансии публикуют как «программист со знанием БД» и «архитектор».
Архитектор — это вообще про другое.

Ну и да, «программист со знанием БД» — это хорошо, только таких (знающих БД лучше, чем специально для нее написанный компилятор выражений в ORM) существенно меньше, чем просто хороших программистов.
1. sql надо блин знать полюбасу:)
2. см выше
3. Если приложение редактор БД почему логика должна быть не в БД? Грош цена такому тестированию которое не учитывает всех слоев.
«sql надо блин знать полюбасу»
Не слишком ли много «надо знать». Голова одна.

«Если приложение редактор БД почему логика должна быть не в БД?»
Если приложение — редактор БД, то логики в нем нет.

«Грош цена такому тестированию которое не учитывает всех слоев. „
Про юнит-тесты вы не слышали? И вообще про деление тестов на компонентные, функциональные, интеграционные, приемочные и далее по тексту?

Какбы настройка автоматического теста с использованием БД где-то на порядок-два сложнее, чем настройка такого же теста с использованием изоляции.
1. Жалеешь голову работай руками :)
2. Это гипербола.
3. Слышали:) интеграционные эффективнее всего хоть и действительно сложнее. Юнит-тесты полезны пожалуй только в тестировании сложных алгоритмов.
«Жалеешь голову работай руками»
Не думая?

«Слышали:) интеграционные эффективнее всего хоть и действительно сложнее. Юнит-тесты полезны пожалуй только в тестировании сложных алгоритмов. „
Уууу… вас не смущает тот факт, что интеграционный тест банально дольше работает? На те же два-три-четыре порядка? А значит, полное покрывающее тестирование системы будет идти нецивилизованно долго?
Тест — это не приложение, он не обязан супер быстро работать, не в этом цель. Цель теста показать ошибки.
«Тест — это не приложение, он не обязан супер быстро работать, не в этом цель. Цель теста показать ошибки. „
Вы, видимо, не читали ни Continuous Delivery, ни The Art of Unit-Testing.

Тест должен работать быстро, потому что иначе никто не будет его выполнять. Потому что тесты, в идеале, должны гоняться после каждого изменения. Если тесты занимают день, никто не будет их прогонять чаще раза в месяц, значит, и информации об ошибках не будет.
Читал, так же еще в таких книгах обычно написано лучше медленный тест чем никакого:) Действительно все тесты не гоняю каждый раз, только связанные с фичей, однако билдстанция прогоняет при каждом коммите и шлет письма если че не так.
«однако билдстанция прогоняет при каждом коммите и шлет письма если че не так. „
Интеграционные? На near-production БД? На все сценарии использования?

У нас одно развертывание образцовой БД занимает дольше, чем имеет право идти коммит-тест.
Ресторится девовская бд, каждый тест подготавливает себе данных и подчищает за собой. 100 тестов 5 минут. 80% тестов интеграционные.
100 тестов? Выглядит, гм, занятно. Интересно, какое у вас покрытие.

(ну и да, у меня тут некий проектик на 119 тестов, проходит за 9 секунд)

(впрочем, у нас БД для интеграционных тестов ресторится дольше)
А зачем у вас данные о метро в RentOfferBase денормализованы? Ведь вместо станции и линии можно было бы хранить только станцию
В основном для удобства использования поиска-фильтраци, а так же удобно показать пример фильтра
Хорошая ли вещь ORM? Задумка хорошая. А вот реализация отвратительная.

Но у нас, например, в PHP, все ORM реализованы из рук вон плохо, неэффективно, тащат в рантайме десятки классов, делают кучу лишних действий и т.д. А как с этим дела в .NET? Подозреваю, что так же.

Вот сделают нормальную компиляцию/генерацию быстрого ORM кода, чтобы из описания предметной области генерировались минималистичные классы моделей — тогда будем использовать. А так выгоднее руками запросы писать, хотя бы тормозить не будет.

Например, почему вы на каждое поле создаете приватное поле, сеттер и геттер? Памяти лишней много? Зачем этот мусор в коде? Почему нельзя хранить все поля в приватном ассоциативном массиве и сделать ОДИН сеттер и ОДИН геттер (типа setValue(field, value))? А если вам надо перебрать к примеру 1000 записей в цикле, вы что, будете (неявно) создавать 1000 объектов, читать данные из БД и вызывать 1000 * 20 сеттеров? Потом вызывать всю эту тьму геттеров и сеттеров, чтобы их обновить? Вот еще одно доказательство, что ORM пишут непродуманно и как попало.

Более того, почему вообще 1 модели должен соотвествовать один класс? Можно сделать 1 класс на всю предметную область (класс с именем типа Storage, который в себе уже все инкапсулирует). А у вас по тяжелому объекту на каждую запись в таблице создается. Так ресурсов никаких не хватит.

Также, комментарии реализованы неудачно. Что за уродливый синтаксис? В нем XML-мусора больше, чем полезной информации. Есть же JavaDoc.

Также, неудачно названо поле onFieldChange. Ну кто, кроме индусов из майкрософт, мог придумать такое тупое название? Какой еще change? В нормальном мире это называется зависимостью между полями, или вычисляемым полем (calculated field).

События, опять же, приделаны как какой-то костыль. В правильном описании модели между полями могут быть взаимосвязи/зависимости, и события не нужны (так как если поля связаны функциональной зависимостью, они обновляются сами, без всяких событий).

> Например, одна компания разработала модуль Адресов и поставляет его как библиотеку, мы не имеем доступа к исходному коду этой библиотеки и хотим добавить какое-нибудь поведение на форму, например при выборе некорректного адреса предупреждать.
> Для этого мы можем написать событие формы, которое будет применено к внешнему компоненту.

Это вообще треш. Приделывать костыли к чужому закрытому коду. Это вы подаете как преимущество майкрософтовской платформы? И развен такие проверки и предупреждения не должны быть реализованы где-то в контроллере, а не в хранилище?

Также, у ORM есть фундаментальный недостаток. Так как ORM создает «видимость» (абстракцию), что persisted объект является обычным объектом, программист работает с ним как с обычным объектом (а не с таблицей SQL), что порождает неэффективные SQL-запросы, которые могут делать адские джойны, выборку не по индексу, которые пытаются неуклюже лечить огромным кешем, держа все активные объекты в оперативной памяти, или держа списки изменившихся свойств (чтобы сохранить их в конце транзакции), что тоже расходно.

Там, где классический разработчик напишет 1-2 простых запроса без джойнов, ORM нагенерирует такое, что даже MS SQL сервер с многотыясчедолларовой лицензией будет скрипеть жестким диском (хотя, если подумать, производителям железа выгодны тяжелые ORM — спрос повышается на SSD).

Подводя итоги, мы видим множество недостатков и просчетов, допущенных при проектировании ORM компоненты. За быструю разработку приходится расплачиваться космической неэффективностью кода. Не представляю, как на такой непродуманной и ненадежной платформе можно вообще что-то писать. Неудивительно, что ентерпрайзный софт ругают все, кто с ним работал.
Был так возмущен, что слов не осталось? :)
Предыдущий комент сам отправился :(
Но у нас, например, в PHP, все ORM реализованы из рук вон плохо, неэффективно, тащат в рантайме десятки классов, делают кучу лишних действий и т.д. А как с этим дела в .NET? Подозреваю, что так же.
Вот сделают нормальную компиляцию/генерацию быстрого ORM кода, чтобы из описания предметной области генерировались минималистичные классы моделей — тогда будем использовать. А так выгоднее руками запросы писать, хотя бы тормозить не будет.

У нас 1 класс на 1 сущность. Реализованы эффективнее, чем большинство написало бы сами.

Более того, почему вообще 1 модели должен соотвествовать один класс? Можно сделать 1 класс на всю предметную область (класс с именем типа Storage, который в себе уже все инкапсулирует). А у вас по тяжелому объекту на каждую запись в таблице создается. Так ресурсов никаких не хватит.

Прощай легкая поддержка, привет АД

Более того, почему вообще 1 модели должен соотвествовать один класс? Можно сделать 1 класс на всю предметную область (класс с именем типа Storage, который в себе уже все инкапсулирует). А у вас по тяжелому объекту на каждую запись в таблице создается. Так ресурсов никаких не хватит.

Хоспади… упаси меня

Дальше просто не смог читать, извините.
> Почему нельзя хранить все поля в приватном ассоциативном массиве и сделать ОДИН сеттер и ОДИН геттер (типа setValue(field, value))?

Дальше не читал. Это такой адский отжиг, что я даже не знаю что вам на это сказать.
«Вот сделают нормальную компиляцию/генерацию быстрого ORM кода, чтобы из описания предметной области генерировались минималистичные классы моделей — тогда будем использовать.»
EF (POCO-генератор или Code First"

«Например, почему вы на каждое поле создаете приватное поле, сеттер и геттер? Памяти лишней много? Зачем этот мусор в коде?»
А что, в .net это как-то влияет на память? Нет, не влияет. Более того, может инлайниться до обращения к полю.

«Почему нельзя хранить все поля в приватном ассоциативном массиве и сделать ОДИН сеттер и ОДИН геттер (типа setValue(field, value))?»
Потому что это не соответствует ООП.

(не говоря уже о накладных расходах на поиск в ассоциативном массиве)

«А если вам надо перебрать к примеру 1000 записей в цикле, вы что, будете (неявно) создавать 1000 объектов, читать данные из БД и вызывать 1000 * 20 сеттеров?»
Да. Это плата за уровень абстракции, удешевляющий разработку.

«Более того, почему вообще 1 модели должен соотвествовать один класс?»
Мммм… ООП?

«Также, комментарии реализованы неудачно. Что за уродливый синтаксис? В нем XML-мусора больше, чем полезной информации. Есть же JavaDoc.»
ORM тут не при чем совершенно, это стандартный синтаксис .net.

«События, опять же, приделаны как какой-то костыль. В правильном описании модели между полями могут быть взаимосвязи/зависимости, и события не нужны (так как если поля связаны функциональной зависимостью, они обновляются сами, без всяких событий).»
Это вопрос используемой абстракции. Событийная менее наглядна (по сравнению с вычисляемыми полями), но дает больше возможностей (например, можно делать валидацию по изменению поля).

«Также, у ORM есть фундаментальный недостаток. Так как ORM создает «видимость» (абстракцию), что persisted объект является обычным объектом, программист работает с ним как с обычным объектом (а не с таблицей SQL), что порождает неэффективные SQL-запросы, которые могут делать адские джойны, выборку не по индексу, которые пытаются неуклюже лечить огромным кешем, держа все активные объекты в оперативной памяти, или держа списки изменившихся свойств (чтобы сохранить их в конце транзакции), что тоже расходно.»
Никто не говорит, что для работы с ORM не нужно представлять себе, как работает БД. Но не забывайте, premature optimization is the root of all evil. И именно этот предоставляемый уровень абстракции позволяет нам проще работать с кодом, уменьшая (воспринимаемый" уровень сложности системы.

«Там, где классический разработчик напишет 1-2 простых запроса без джойнов, ORM нагенерирует такое, что даже MS SQL сервер с многотыясчедолларовой лицензией будет скрипеть жестким диском (хотя, если подумать, производителям железа выгодны тяжелые ORM — спрос повышается на SSD).»
Понимаете ли, любой SSD дешевле, чем человеческое время.
«Потому что это не соответствует ООП.»

Геттеры и сеттеры сами по себе не соответствуют ООП, они нарушают инкапсуляцию. О каком ООП вообще речь?
«Геттеры и сеттеры сами по себе не соответствуют ООП, они нарушают инкапсуляцию. О каком ООП вообще речь? „
Вы МакКоннела читали? Вот использование поля как части публичного интерфейса нарушает инкапсуляцию. А геттеры и сеттеры как раз прячут конкретную реализацию, выставляя консистентный интерфейс.

Более того, в конкретно взятом .net геттеры и сеттеры являются синтаксическим сахаром вокруг абстракции свойства, и уж там-то точно не нарушают ничего.
Подсунуть вместо поля пару функций/свойство это ещё не значит инкапсулировать. Вот, ознакомьтесь
www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html

Кроме того, я тут объектов как таковых не вижу. Это просто записи/структуры.
То, что тут anemic/dto — это другой разговор. А про инкапсуляцию и геттеры-сеттеры все сказано у МакКоннела, раздел 6.2 Good Class Interfaces, секция Good Encapsulation.
lair, спасибо за ответ, хочу дополнить:
А если вам надо перебрать к примеру 1000 записей в цикле, вы что, будете (неявно) создавать 1000 объектов, читать данные из БД и вызывать 1000 * 20 сеттеров?
Если эти объекты невозможно обработать запросом, то в любом случае они будут загружены в память.
При этом мы можем сделать выборку только нужных (например 3-4 полей вместо 20).
События
Они на то и события, чтобы указывать возможность выполнить код.
Для зависимых полей у нас тоже есть решение, виртуальные поля
может выглядеть например так:
/// <summary>
/// Выражение для поля суммы
/// </summary>
private Expression<Func<RentOfferBase, decimal>> SummExpression = a => a.Price * a.Count;
        
/// <summary>
/// Сумма
/// Вычисляемое поле
/// </summary>
[VirtualField("SummExpression")]
public decimal Summ { get; set; }


Там, где классический разработчик напишет 1-2 простых запроса без джойнов, ORM нагенерирует такое

К сожалению, классические разработчики не всегда узнают о новых возможностях, например появился в SQL server новый способ выполнить эффективнее запрос, мы «обучаем» делать это ORM и он везде где может применяет это, не забывая, не требуя времени на исправления во всех местах.
Еще раз простите, я тут вытащил код из одного ПХП проекта… это не вы писали случаем?
pastebin.com/hwFWiayM
Точно не я. Я не использую PHP 4, даю прекрасные английские названия переменным, не использую подчеркивание в начале имени, не люблю imagick и запуск сторонних процессов, и уж точно не написал бы этот нечитаемый ужас.
Не знаю как у Вас, но у нас в php + DoctineORM всё прекрасно. А благодаря использованию мепперов sql генерируется бывает и лучше чем кривыми ручками.

НО… суть не в этом. ORM это не просто абстракция над хранилищем, но и чёрный ящик который помогает скрыть часть бизнес логики. Например, есть таблица с пользователями и softdelete + versionable, ты просто указал использовать этот шаблон и не паришься что кто-то кривым запросом может убить целостность данных.
эх, уютный enterprise мир, где основная проблема какой бы еще интерфейс к SQL навернуть.
У нас все бегают с другими словами: денормализация, мемекэши, nosql, шардинг, мастер нода, слейв нода, разогрев мемкэшей, каскадное падение )
А ещё у нас есть пиченьки!
еще спорт зал и страховка… Вот тебе и choose life )
хочу печеньки, спортзал, старховку, шардинг, каскадное падение, отсутствие SPoF, map/reduce. Куда идти?
Не знаю за что можно так не любить ОРМ. Все в одном месте. Валидация — через аннотации. Структура — через метаданные. События — через анноациии и методы внутри моделей. Что ещё нужно для счастья не понимаю.
Автор, а ответьте-ка, как в вашем хваленом ORM реализуются следующие банальные вещи?

1) Допустим, мы хотим сделать в нашем приложении комментарии. Очевидно, что у комментария есть автор, но если хранить только id автора, и брать его имя из дополнительной таблицы JOIN'ом, то любая база данных затрещит, потому мы хотим хранить в таблице комментариев дополнительно имя и ник автора, сохраняя их в момент создания комментария. Вопрос: можно ли в указанном ORM с помощью аннотаций добавить псевдо-поля «authorName» и «authorNickname», чтобы они формировались при создании/обновлении комментария на основе поля authorId и таблицы пользователей. Без событий и костылей. Это поля, которые просто кешируют данные из связанной таблицы (authors).

2) мы хотим иерархические комментарии, чтобы список комментариев выбирался из БД легким индексируемым запросом без джойнов. Для этого как нельзя лучше подошли бы Materialized Paths. Можно ли аннотацией без костылей и событий как-то сказать классу, что он хранит иерархические записи с использованием MP?

3) Теперь мы хотим на главной, под анонсом поста, выводить количество комментариев к нему. Очевидно, что если делать для каждого анонса SELECT COUNT(*), база прикажет долго жить. Потому мы хотим добавить аггрегатное псевдо-поле commentCount, которое будет автоматически инкрементиться/декрементиться/обнуляться при создании/удалении/перемещении комментария? С помощью аннотации типа [field virtual method=aggregate function=count relatedEntity=comments]? С помощью какого-нибудь свойства связи posts to comments?

Все задачи, которые я привел, банальны и всречаются на каждом втором сайте. ORM, в котором нет встроенных средств для их решения, никак нельзя назвать современным, актуальным и прочее. Такой ORM провоцирует разработчика на написание неэффективного кода (вместо того чтобы предложить встроенный протестированный инструмент для решения задачи). Он годится разве что для сайтика в локалке на 3 пользователей, а не серьезных проектов. А его разработчики видимо перечитали Макконнела и окончательно потеряли связь с реальностью.
2) мы хотим иерархические комментарии, чтобы список комментариев выбирался из БД легким индексируемым запросом без джойнов. Для этого как нельзя лучше подошли бы Materialized Paths. Можно ли аннотацией без костылей и событий как-то сказать классу, что он хранит иерархические записи с использованием MP?

А я считаю, например, что лучше подходят Nested Sets. Для этих целей у нас есть специальный модуль, который обрабатывает все сущности, реализующие интерфейс IHierarchical where T: Entity

А теперь вы ответьте-ка, как вы сделаете то же самое без ОРМ, без триггеров(которые на самом деле события).

3) Теперь мы хотим на главной, под анонсом поста, выводить количество комментариев к нему. Очевидно, что если делать для каждого анонса SELECT COUNT(*), база прикажет долго жить.

Селект всех постов разом с подсчетом коментов? Про Group By слышали?:)
Ну, а если сделать, как вы предлагаете, то опять же, есть события сущностей.
> А я считаю, например, что лучше подходят Nested Sets.

Не пишите такие вещи, если не разбираетесь. С Nested Sets вставка комментария в начало приводит к пересчету left/right key у всех сотен следующих за ним (а ведь это индексированные поля).

> А теперь вы ответьте-ка, как вы сделаете то же самое без ОРМ, без триггеров(которые на самом деле события).

Очевидно, я возьму правильный (воображаемый) Data Access Layer, в котором это есть, или напишу плагин, если такового нет.

> Селект всех постов разом с подсчетом коментов? Про Group By слышали?:)

Зачем делать Group BY, заставляя сервер пересчитывать тысячи комментариев, когда можно хранить циферку в табличке. И делать селект из ОДНОЙ таблицы без всяких джойнов и груп бай. И вместо шкафа из мощных серверов поставить один. Сдается мне, вы как раз разрабатываете сервисы для локалки на 3 человек. Ну что вы пишете такое.

Слушайте, а если вам надо будет вывести рейтинг постов с наибольшим числом комментариев, вы тоже Group BY и сортировку (или вложенные запросы? как там принято?) будете делать на таблицах с десятками тысяч постов и миллионами комментариев?
>Не пишите такие вещи, если не разбираетесь. С Nested Sets вставка комментария в начало приводит к пересчету left/right key у всех сотен следующих за ним (а ведь это индексированные поля).

Могу сказать вам то же самое. На одно добавление коментария будет 1000 его просмотров, а строить дерево по нестед сетам гораздо приятнее.

>Очевидно, я возьму правильный (воображаемый) Data Access Layer, в котором это есть, или напишу плагин, если такового нет.

Что вы понимаете под DAL? ORM — это тоже DAL.

>Сдается мне, вы как раз разрабатываете сервисы для локалки на 3 человек. Ну что вы пишете такое.
Я разрабатываю сервисы для тысяч. Мне гораздо проще закешировать тот самый запрос(а если быть точнее, весь метод, возвращающий данные для главной страницы), чем денормализовывать базу. Видимо, вы кроме sql ничего слаще не пробовали?:)

>будете делать на таблицах с десятками тысяч постов и миллионами комментариев?
десятки тысяч постов — это просто смешно) такие объемы данных — это тестовые данные, не более :)
А почему вы считаете события костылями?
действительно, задача очень даже типовая и встречается часто, и вот как бы я её решил:
1. Выделил бы интерфейс для сущностей в которых необходимо кэшировать ник/имя
public interface  IWithCachedUser
{
    /// <summary>Ссылка на пользователя</summary>
    User User { get; set; }
        
    /// <summary>Имя пользователя (для кэширования)</summary>
    string UserName { get; set; }
        
    /// <summary>Ник</summary>
    string UserNick { get; set; }
}
2.Написал бы простой код проставляющий значения этих полей:
public class CacheUserEvent<TEntity> : IEntityEvent<TEntity>
    where TEntity : EntityBase, IWithCachedUser
{
    public void AfterSave(TEntity item)
    {
        item.UserName = item.User.Name;
        item.UserNick = item.User.Nick;
    }
}<source>
Теперь для всех классов, реализующих этот интерфейс есть кэширование и не обязательно делать джойны.
Все, о чем вы пишете — это оптимизация по производительности. Такая оптимизация практически всегда нарушает и нормализацию, и «хорошее проектирование». Соответственно, такие вещи можно и нужно делать на уровне БД, потому что оптимизируем мы БД.
Понятно же что ORM не для хайлода. Есть задачи где ORM необходим, и есть где просто не нужен.
Ну не всегда так, после того как на одном высоконагруженном проекте внедрили BLToolkit провел нагрузочное тестирование и остался очень доволен.
BLToolkit очень легковесный ORM я бы сказал. Особенно по сравнению со всем остальным в дотнете.
ormbattle.net/ вот тут есть неплохое сравнение, правда довольно старое
code.google.com/p/dapper-dot-net/ один хороший легковесный ORM (конечно он больше маппер, чем полноценная ORM) — с очень хорошим «портфолио» в виде сайта хайлоад сайта stackoverflow.com.
Почитал все комментарии — выводы:
— отношение к ORM-решениям у разных людей может быть совершенно разное — все очень сильно зависит от опыта работы с конкретными решениями и специфики проектов, над которыми они работали.
— очень многие из этих людей готовы отстаивать свою точку зрения, несмотря на понимание того, что единой точки зрения тут явно быть не может :) (все очень сильно зависит от массы факторов).

На мой взгляд, что ORM обычно эффективны в сценариях, когда скорость разработки очень важна. На мой взгляд, связь примерно такая:
— Если производительнось исключительно важна (вы делаете облачный сервис, который будет обслуживать миллионы клиентов), вероятно, вам лучше отказаться от сложных ORM, а возможно, и от SQL DB вообще. Ваши лучшие друзья — memcached, Redis, C++ и т.п., т.к. расходы на разработку в данном случае могут быть нивелированы расходами на эксплуатацию.
— Если для вас крайне важна скорость разработки — берите любое более-менее известное ORM-решение, использование которого в вашем конкретном случае даст максимальный эффект. В производительности вы потеряете не так уж и много, но заметно ускорите сам процесс разработки.

В промежуточном сенарии я бы все же выбрал ORM, но пользовался бы SOA/DTO для реализации уровня абстракции, т.е. заранее предусмотрел бы возможность отказа как от ORM, так и от текущей БД.

Возможно, ответ слегка biased, т.к. я — один из разработчиков DataObjects.Net.
И, раз уж здесь была затронута тема о том, что стоит выносить на уровень БД, озвучу свое мнение:

IMO, лучше всеми возможными способами избегать использование хранимых процедур, триггеров и прочего кода на SQL, сильно связанного с логикой работы приложения. Причин тому несколько, вот главные из них:
— Сложный код на SQL (все, что не является запросом) — ужасен. Его отладка, профайлинг и поддержка (ревизии в репозитории и т.п.) в разы дороже, чем в случае с кодом на любом современном языке.
— То, что код, работающий в процессе БД спасет вас в случае роста нагрузки — миф. Скорее спасет partitioning / sharding, и денормалиация (пример: goo.gl/dN7tl ). И конечно же, разработчики, хорошо понимающие, чем отличается full scan от index seek.
Одинэсники смотрят на этот джихад с умилением. =)

А если серьезно — ORM рулит и педалит на сложных и динамичных проектах, где скорость и легкость разработки важнее некоторого выигрыша в рантайме. Просто не передать словами тот кайф, когда не нужно забивать голову деталями хранения и получения данных.

А еще ORM прекрасно ложится на NoSQL, что дает огромный прирост производительности без малейших усилий со стороны разработчиков. =)
> ORM прекрасно ложится на NoSQL

Вот это как раз неверно :) В см. как правило, там не нужен ORM, т.к. в БД можно сохранять объекты «как есть». Соответственно, не нужен маппинг, сложные преобразования запросов и т.п.

Т.е. те плюсы, которые дает ORM РСУБД-разработчикам, NoSQL-разработчики получают от client-side API.
ORM почти полностью скрывает СУБД от разработчика, так что пофиг, что там — SQL, не-SQL, реляционная или еще какая. Для не-SQL процесс преобразования объектов в сущности хранилища намного проще и быстрее, но полностью не исключен. Все-таки нужно как-то сериализовать объект в нужном виде, выделять ключевые и индексируемые поля, и все такое…

Возможно, при этом теряется смысл буквы «R» в слове ORM. Но я не знаю другой, более подходящей по смыслу аббревиатуры.
Да, это скорее терминологический вопрос — в см. это уже не ORM, т.к. сложных преобразований (в том числе и для запросов) NoSQL-клиенты не делают. Как правило, все сводится к change tracking и сериализации.
Sign up to leave a comment.

Articles