пятница, 8 марта 2013 г.

Про альтернтивы ORM

Intro

   История эта началась в первой половине 2000-х. В смысле началась именно для меня. На самом деле ORM движки появились раньше чем я о них узнал, что в общем очевидно, но погружаться в историю вопроса я сейчас не очень хочу, да и не очень то и надо. Итак была проблема - Ява имела замечательный интерфейс с БД, возможно лучший из тех что были на тот момент и может быть все еще лучший из тех что есть сейчас. Но при этом, например, такая простая операция как превращение результатов запроса
select fist_name, last_name from users u where u.sex = 'M' 
в массив была адски многословна. открыть коннект, открыть ResutSet, в цикле пробежаться, выбрать поля о одному ни разу их не перепутав (привет нумерации полей). И потом все столь же аккуратно все закрыть.  Ну ни разу не сложно но муторно.
   Надо сказать что попытки решить проблему были, например SQLJ который давайл возможность писать SQL прямо в Ява коде. Выглядело все вот так
public static void singleRowQuery(int id) throws SQLException {
      String fullname = null;
      String street = null;
      #sql {
         SELECT fullname,
            street INTO :OUT fullname,
            :OUT street FROM customer WHERE ID = :IN id};
      System.out.println("Customer with ID = " + id);
      System.out.println();
      System.out.println(fullname + " " + street);
   }
Но заводилось это достаточно муторно и в общем так и не взлетело.

   Hibernate для меня (и видимо не только для меня) на тот момент был самым простым, хорошо документированным и скажем так идеологически верным способом. После знакомства с ним во всех проектах лет наверное лет 5 я  в базу данных никак кроме как при помощи Hibernate (а затем JPA) не обращался. Потом таки наступило некоторое отрезвление и я задумался так ли ORM хорош? Ответ который я нашел для себя был примерно такой - ORM хорошо, но в большинстве случаев лучшей альтернативой является Spring JDBC. Далее я постараюсь аргументировать свою точку зрения. Про плюсы ORM я писать не буду, предполагается что вы их и так знаете. Так что из чтения данной статьи может сложиться впечатление, что ORM - это очень плохо. Нет ORM вполне хорош, но для большинства проектов его плюсы все же наверное  перевешиваются минусами.

Почему я больше не люблю ORM

1. Его надо изучать

Да, документация скажем на Hibernate не такая уж и большая и очень легко читается, но все таки она есть и ее объем несколько сотен килобайт. Опыт собеседования >50 Ява разработчиков показал, что из всех кто работал с Hibernate на ряд продвинутых вопросов (про join fetch например) по этому фреймворку смогло ответить только 3 человека. Это видимо значит, что остальные используют Hibernate без понимания некоторых побочных эффектов и важных моментов. Это может приводить к существенному падению производительности и если не повезет хитрым багам 
Чтобы не быть голословным - "любимый" баг джуниоров, это закрытая сессия и отвязанный объект с лениво инициализированным полем. Если поле иногда инициализируется  (прокликивается), а иногда нет эффект получается очень веселый. 
Из того же разряда - "вечная", не закрываемая сессия кеш первого уровня которой распухает от объектов. При разработке все хорошо но как только база наполняется реальными данными все рушится.

2. HQL != SQL

   В наши времена трудно найти разработчика не знающего SQL. Но чтобы пользоваться Hiernate знания SQL мало, надо знать HQL, в общем он достаточно прост, но его семантика все таки несколько отличается от SQL. В лабораторных условиях этого не заметно, но когда вам надо перевести с SQL на HQL запрос из 55 строк (что для реальных баз не такая уж и редкость бывают запросы и на 155 строк), вы понимаете, что не все так просто.
   Еще одна проблема заключается в том, что не всегда один HQL запрос на фазе исполнения превращается в один SQL. Иногда он компилируется в два и более запроса. Вроде бы это не страшно, но если мы говорим о высоко-нагруженном приложении это значит, что Вам нужен в два раза более производительный сервер, что может стоить например +$25000 например. Увы сервера БД очень дорого масштабируются.
   Также SQL выражение вы всегда легко можете проверить и отладить в консоли, посмотреть план исполнения или послать скайпом админу заказчика чтобы он его проверил на боевой базе. С HQL все сложнее.
   Конечно в современных IDE есть HQL консоль, но заставить ее работать  не всегда просто (зависит это в основном от структуры проекта), а посылка HQL админу скайпом "на проверить" - это явно из области фантастики. Да и план исполнения можно конечно посмотреть, но весьма не тривиально.

3. Легко выстрелить в ногу

    В ORM вы работаете с объектами, их полями и коллекциями. Это удобный уровень абстракции, но как любой уровень абстракции он скрывает ряд важных деталей. По идее грамотный синьор который понимает как все работает может писать код с учетом этих особенностей и все будет хорошо. Но в жизни так не получается, о чем-то можно забыть, о чем-то не знать, где-то вообще третий человек пишет код который оперирует объектом не зная что к объекту привязана живая сессия и простой for по коллекции это на самом деле запрос в БД. В общем-то совершенно невинные вещи неожиданно могут приводить к странным побочным эффектам.
Тут самый показательный случай произошел с двумя коллегами - когда построение одного экрана первым товарищем "стоило" около 500 запросов, после чего другой человек переписал все это в 5 запросов. 

4. Сначала надо все описать

   Прежде чем послать запрос в БД надо описать маппинг. Это вроде бы очевидно и если проект и БД растут вместе, постепенно, то это не заметно. Но если у вас есть уже БД в которой 100 таблиц, Вам надо сделать приложение в котором скажем 10 select-ов, два update-а и 2 insert-а, причем выборка данных не простая и затрагивает в разных запросах, в общем сложности скажем 50 таблиц из 100, то перед началом работы Вам надо без ошибок описать мапинг 50 объектов. Обычно это весьма трудоемкое и не терпящее ошибок занятие.

5. Мелочи

   Есть еще ряд мелочей таких как: не меленький объем подключаемых библиотек, некоторые накладные расходы на старте, чуть большая нагрузка на процессор и т.п. Но в общем они как раз обычно не так критичны и я на них останавливаться подробно не буду.

Что вместо?

   На текущий момент, кажется что хорошей заменой ORM является Spring JDBC - это очень легкая простая, stateless обертка над JDBC позволяющая писать запросы к БД и получать результаты в одну строчку. Например так
int rowCount = jdbcTemplate.queryForInt("select count(*) from t_accrual");
Тут мы открываем конект, выбираем данные и закрываем все что открыто. Можно использовать параметры и мапить объекты
Actor actor = (Actor) this.jdbcTemplate.queryForObject(
    "select first_name, surname from t_actor where id = ?",
    new Object[]{new Long(1212)},
    new RowMapper() {
        public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
            Actor actor = new Actor();
            actor.setFirstName(rs.getString("first_name"));
            actor.setSurname(rs.getString("surname"));
            return actor;
        }
    });
В общем все очень компактно, функционально и практически zero overhead. Это конечно не единственная альтернатива ORM, но вполне хороший вариант.

Выводы

Ну я все рассказал. Понятно что можно и так и этак и вообще как-то по другому. В общем решайте сами. Не буду писать мантры, enjoy!