[Moscow.pm] Размышления на тему ORM и вообще работы с БД
Ivan Petrov
i.petro.77.00 на gmail.com
Ср Окт 26 09:26:47 PDT 2011
сделали мы тут цельный проект на DBIx::Class. С точки зрения
попробовать последний. До этого всегда использовали DBI, который
использовала модель в системе MVC.
Впечатления плохие, однако появились мысли куда надо двигаться чтобы
было хорошо. Я их тут изложу, а вы покритикуйте.
В первую очередь зачем как бы нужен был этот DBIC.
- конструирование SQL-запросов
- уход от смешивания кода Perl с кодом SQL
- выборка объектов, а не хешей из БД, которые можно расширять своими
методами
- упрощение кода моделей с точки зрения что в простых случаях вообще
не делать выделенную модель
Ничего не забыл?
По результатам проекта (проект был довольно большой и дальше еще будет
долго) я понял следующее:
1. даже в простых случаях DBIC на роль модели не годится. То есть в
любом случае нужно делать выделенную модель, пусть даже там один
resultset('Name')->find. Ибо тесты. При их написании мы применяем
способ "подменить модель на стадии тестирования".
2. DBIC не предоставляет хорошей абстракции над БД. Все равно
JOIN'ы, SELECT'ы торчат местами (например join_type => 'left' в has'ах
итп). То есть оперировать хранилищем как некоей абстракцией в реальном
случае не получается.
3. Конструктор SQL-запросов крайне страшен. Работали мы с постоянно
включенной отладкой (выводом всех делающихся запросов в лог). С одной
стороны есть множество вещей которые на DBIC сделать крайне непросто
(например многие аггрегаторные вещи), с другой стороны когда
начинается работа с более чем двумя таблицами он зачастую мутит такие
страшные вложенные SQL-запросы что волосы дыбом становятся, а с
третьей стороны использование фич конкретной БД становится затруднено.
В итоге пришли к тому, что в сложных случаях приходится делать в БД
view'ы только из за того что от DBIC добиться генерации нормального
запроса очень и очень сложно или невозможно. Набор опций при выборке
может быть больше самого SQL-запроса, если его просто написать.
Ну вот и в свете того, что код работы с DBIC все равно располагается в
моделях, конструировать эффективные SQL-запросы он все равно не умеет,
то из плюсов его использования остаются только:
- выборка в объекты и итераторы по ним
- уход от смешивания Perl и SQL
Соответственно размышляя над всем этим мы пришли к тому, что
1. построить качественный автогенератор SQL запросов на все случаи
жизни (или по кр. мере на большинство) невозможно, поэтому его не
стоит и пытаться сделать
2. из за п.1 бОльшая часть DBIC становится не нужна, а надо делать
некоего помощника, который бы человеку просто помогал удобно работать
с БД.
то есть надо
1. написать свой итератор, работающий поверх массива или
хеша - выборки из БД.
2. написать базовую обертку над одной строкой выборки (доступ в виде
методов)
3. Используя 1 и 2 написать над DBI класс, который выборки блессает в
итераторы и объекты
4. решить вопрос с выносом SQL-кода из перловых модулей. То есть
обертка 3 должна уметь работать с SQL, располагающимися в файлах.
Вынос SQL в файлы очень плохо соотносится с дефолтными плейсхолдерами
DBI. поскольку они позиционные.
Вывод:
5. надо реализовывать именованные плейсхолдеры
ну и попутно
5.1 реализовать плейсхолдерные (темплейтные) выражения для типовых
вещей, как-то
INSERT ... VALUES (?,?,?),(?,?,?),(?,?,?),...
или
SELECT ... WHERE id IN (?,?,?)
то есть подстановки массивов.
Соответственно исходя из этого родился набор модулей, который в
ближайшее время, если кому-то будет интересно, мы можем выложить на
CPAN (там еще работы на неск. дней осталось), который все это делает:
1. по дефолту блесает строки-выборки в объект, у которого три
предопределенных метода: new, is_changed, iterator. Плюс методы
совпадающие с именами выбранных столбиков.
2. блесает наборы-выборки в объект позволяющий делать проходы по
выборкам итп (совместимо с DBIC'шными ->all, while(...->next) итп
3. в п. 1 и 2 позволяет указывать свои классы для итераторов и выборок
4. позволяет вместо SQL использовать имена файлов SQL в предзаданной
директории (по аналогии с темлейтами любого вебпроекта)
5. реализует следующий набор подстановок в SQL
?{путь} - просто подставляет переменную
?@{путь} - подставляет массив переменных (через запятую)
?%{путь}{поле,поле,...} - подставляет массив переменных (через
запятую) из массива хешей
ну и на вс случай сделали
?sub{перловый код}
А так же набор условных подстановок вроде
?if[de]?{путь}{ блок который вставится }{else-блок}
условия - if - истина
ifd - определен
ife - имеется
Блоки можно вкладывать друг в друга.
пути - по сути путь к переменной во входном хеше, то есть
$dbh->do($sql, values => { ids => [1, 3, 5] }, abc => 'def');
путями являются
'values'
'abc'
'values.ids'
и так далее.
поддерживаются в путях и объекты
path.to:method - будет вызван метод объекта, а значение им
возвращенное будет вставлено в SQL.
В итоге что получили:
1. SQL вынесен в отдельный каталог (отдельные каталоги) примерно так
же как вынесены темплейты в любом вебпроекте
2. типичные SQL-вещи делаются просто. Сложные тоже можно делать.
3. Поскольку итератор по именам методов постарались сделать
совместимым с DBIC то замена одного на другое не вызывает больших
трудностей.
ну и возьмем пример:
скажем приходит от пользователя форма
$filters = { region => 'Москва', street => '' };
То есть он фильтр по региону заполнил, а фильтр по улице нет
запрос может выглядеть так
SELECT
*
FROM
tbl1
LEFT JOIN tbl2 ...
WHERE
something = ..
?if{filters.region}{ AND region = ?{filters.region} }
?if{filters.street}{ AND street = ?{filters.street} }
и так далее.
Получается SQL перестраивается в зависимости от того что там вводит
пользователь.
Осталось нерешенными некоторые типовые вопросы, но мы их планируем
порешать в ближайшее время (например подставновки частей в
like-секции)
соответственно вопросы:
1. кому-нибудь интересна подобная система, стоит класть на CPAN?
2. может на эту тему есть что-то готовое да мы велик очередной ваяем?
тогда ткните указкой:)
Подробная информация о списке рассылки Moscow-pm