воскресенье, 3 июля 2011 г.

Так нужен ли ORM в крупном и сложном Enterprise-проекте?

Недавно на Хабре мелькал вопрос - так нужен ли на самом деле ORM в крупном и сложном проекте? Ведь он часто медленный, громоздкий, поддерживает только некоторое подмножество SQL, не поддерживает специфический и очень удобный синтаксис, например, Oracle (тот же connect_by для иерархических запросов) и прочее и прочее.


Высказывалось мнение, что ORM в действительности нужен только в примитивных проектах, для сокращения размера кода, а в реально большом и сложном проекте лучше обойтись без него. Я скажу за большие проекты - за мелкие пусть скажут другие :) Оговорюсь, что рассуждения и примеры строю на Java / Oracle, классическая связка.


Итак.


Сам по себе ORM, именно как mapping, в крупных проектах нужен как раз очень сильно.


Представьте себе — у меня есть очень крупная система, и есть в ней таблица orders, на ней скажем, 50 колонок (на самом деле у нас 150, ну да ладно. Нормализаторы, молчать! Про нормальные формы я тоже знаю). И вот надо представьте, что вам выбрать один ордер и показать его на экране. Допустим, вы пишете селект, вопрос - дальше что делать с его результатами, в промежуточном слое, в server-side Java? Вы не же вызываете хранимую процедуру или запрос напрямую с, скажем, JSP страницы (я надеюсь), вам все равно надо получить данные и передать их как-то, в виде какой-то структуры данных.


Так что же, передавать их в виде массива, ArrayList-a, ассоциативного массива как [имя колонки : значение]? Это громоздно, неудобно, и очень легко ошибиться. А если вам надо несколько ордеров, тогда что, создавать вложенные коллекции для конвертации результатов? Это по своему вредному влиянию будет уже напоминать известный антипаттерн "Строковая типизация". Использовать коллекцию векторов или ассоциативных массивов там, где можно использовать коллекцию объектов типа Order.

Мы нашли пример случая, нам нужен объект Order, имеющий все нужные property, и, следотельно, нам нужен код, который умеет конвертировать результаты SQL запроса в объекты (коллекцию таких объектов).

Далее, очевидно, что писать руками все запросы трудно и нудно, легко ошибиться, т.к. в Java они будут представляться или в коде в виде строк (а значит, никакой статической типизации и compile-time проверок), и их надо держать либо в Java коде (если они мелкие, и все равно получается замусоривание Java кода), либо, если побольше, выносить в отдельные XML файлы.


Здесь небольшое лирическое отступление. Спор - держать ли SQL код коротких запросов прямо в Java коде, или же выносить  их в отдельные файлы в Enterprise-e идет уже долго. 
Аргумент за то, чтобы держать из внутри Java кода - вместе используемый код находится рядом, код SQL запроса легче найти и посмотреть, чем если он лежит где-то отдельно, в случае изменений в VCS надо будет не забыть изменить только один файл, а не два.
Аргумент за разделение по разным классам - многострочные SQL запросы сильно замусоривают Java код (в том числе потому, что Java не поддерживает полноценно многострочные строковые литералы в отличие от, скажем, того же Groovy) + если они определены как константы класса, то при изменении содержимого этой константы сделать java hotswap, например, уже не получится. Я сам обычно смотрю по обстоятельствам :)


В общем, ORM в больших проектах нужен для упрощения рутинной части. Без него — никуда :)

Безусловно, обойтись ТОЛЬКО ORM не получится. Есть у нас масса мест, где сложная логика написана в запросах хранимых процедурах в 500-1000 строк на PL/SQL, ибо написанная через ORM /Java она бы занимала в 10 раз больше места и работала в 2 раза медленнее (при этом, она была бы еще и менее понятна, т.к. есть такая логика, которые в терминах реляционной алгебры описывается проще, чем в терминах ООП :), и такая логика ложится на ORM со скрипом). Сколько нибудь сложные запросы с подзапросами, юнионами, хитрыми джойнами тоже писать через чистый ORM громоздко. Оптимизировать запросы, работающие с таблицами где, хотя бы, несколько сотен миллионов записей, без доступа к планам SQL оптимизатора и статистики/средствам мониторинга уровня СУБД (для Oracle это Explain Plan, tkprof, Grid Control) тоже крайне сложно. Так что без SQL тоже — никуда :)


Можно поговорить о том, как все-таки немного подсластить работу с чистыми SQL-запросами в крупном проекте.


Один из вариантов - использование макросы. Пишется свой фреймворк для написания SQL макросов (в сущности, движок для выполнения умеренно примитивных парсеров, позволяющий людей писать собственные макросы и парсеры к ним). Макросы могут помочь решать следующие задачи:
 - Вынесение реюзабельных частей запросов (обычно, подзапросов) в отдельные скажем так, именованные SQL-компоненты, и включение их затем в запросы с помощью макроса
 - Унификация некоторых надоедливых различий в синтаксисе SQL, например когда параметр запроса может быть скалярным числом, а может быть массивом, тогда вы пишете макрос, которые абстрагирует вас от отличия между t.property = x и t.property in (x1, x2)
 - Добавление предикатов в where clause в целях безопасности
 - Оборачивания всего запроса в SELECT * FROM (query body) t order by.. для унифицированной поддержки сортировки и paging-a


и многое другое.


Да, и еще. Я ни слова не сказал тут о совместимости с разными СУБД, т.к. это часто ненужное требование. Большие и сложные системы активно использует СУБД-specific фичи (расширения синтаксиса SQL, синтаксис процедурных расширений, таких как PL-SQL, T-SQL, pgplsql вообще отличается очень сильно, схемы секционирования и прочая, и прочая), и требования писать SQL-код, одновременно совместимый с Oracle/MSSQL/DB2 часто не ставится (я перечислил тут классические признанные Enterprise-level СУБД, поклонникам остальных просьба не обижаться). Все понимают, что это — огромное усложнение и удорожение системы, далеко не всегда оправданное. Подлинная независимость от серьезного вендора, такого например, как Oracle или IBM — стоит дорого, очень дорого. А часто ли она реально нужна?

6 комментариев:

  1. В деле решения задачи ORM, мне очень понравился MyBatis. С одной стороны ORM, так как данные из РСУБД маппятся на POJO, с другой стороны програмист сам все маппит. То есть можно связать структуры классов сущностей с любой, даже ненормализованной, БД. Правда приходится писать больше кода по сравнению с JPA, и нет всяких адаптеров, так как это не стандарт.

    ОтветитьУдалить
  2. мы используем прилично кастомизированный хибернат в большом проекте(более 1000 таблиц). Независимость от СУБД у нас требование, есть клиенты на МS SQL, Postgres, Oracle. Но вместо HQL, мы разработали гораздо более функциональный язык запросов, на базе antlr, плюс кучу разных фич типа персистентных Java интерфейсов, которые мэпятся на VIEW. Этого языка и фич хватает практически всегда, но есть случаи когда на уровне базы было бы гораздо приятней работать.

    ОтветитьУдалить
  3. Это интересно, про свой язык. Прям полноценный язык запросов? Чем он лучше HQL? Насколько сложно его поддерживать? Как у него со скоростью работы? С обработкой ошибок?

    ОтветитьУдалить
  4. Да своя грамматика.
    На основе(копипаст изначальный с HQL)
    http://www.antlr.org/wiki/display/ANTLR3/Interfacing+AST+with+Java
    лучше HQL тем что он наш и мы его синтаксис постоянно допиливаем под наши нужны, продолжая сидеть на нескольких СУБД. у HQL много ограничений, условия на джойны нельзя, юнионы, вьюшки и т.п., я уж не припомню всех косяков, давно не писал на нём. =) Работает это все через хибернатовые нативные запросы.
    Из последнего планируем добавить иерархические запросы.

    ОтветитьУдалить
    Ответы
    1. Интересно! А можно с вами как-нибудь связаться, пообщаться более предметно и интерактивно на эту тему? :)

      Удалить
  5. Извиняюсь за поздний ответ, я тут под анонимом.
    Можно пишите вопросы на weblogic@mail.ru
    Из последнего отгребли с различными имплементациями JOIN inside DELETE
    в различных базах. Будем прикручивать.
    Вообще если решите пойти этим путем, то сложностей будет много.
    у нас эта часть платформы развивалась более года, а не сели как надо написали.
    Плюс у нас немного специфичная схема базы, заточенная под наш DQL.

    ОтветитьУдалить