С чего все началось
Во время выполнения очередного проекта мне пришлось работать с Битрикс ORM, при этом параллельно в системе был инстанс Laravel и его EloquentORM. Две разные ORM работали с единой базой данных. Не буду вдаваться в причины, по которым был выбран такой подход, и воздержусь от его оценки. Суть в том, что мне приходилось одновременно работать с двумя принципиально разными системами. Этот опыт привел меня к фундаментальному выводу: ORM — не для меня.

Проблема ORM
ORM во всех фреймворках одновременно и похожи, и различны. Основные различия — в синтаксисе: одни и те же ключевые слова в разных ORM используются по-своему. Запомнить все нюансы невозможно. До сих пор попытки сделать JOIN в ORM Битрикса вызывают у меня большие трудности. При этом у Битрикс ORM есть определённые ограничения, о которых желательно знать заранее.
ORM сам по себе неплох, но только для простых операций: его удобно использовать для быстрого обновления и получения данных из таблиц, настраивая модели данных (мапперы) и работая с ними как с объектами в языке программирования. Но часто использование ORM становиться мучительным.
Какие аргументы используют сторонники ORM подхода?
Защита от SQL-инъекций
Объектно-ориентированный подход
Абстракция от БД
Проверка типов
Миграции схемы БД
Кэширование
Разберем каждый аргумент по порядку.
Защита от SQL-инъекций — это параметризированные запросы. Технология, при которой каждый параметр, динамически вставленный в SQL-запрос, экранируется. Практически везде, где есть драйверы для работы с базой данных, есть параметризированные запросы.
Объектно-ориентированный подход. Во время разработки создаем классы и получаем объекты, которые соответствуют таблицам в базе данных. Действительно, бывает удобно использовать связи между объектами, избегая JOIN конструкций, но часто это дополнительно усложняет код, особенно если ты не очень знаком с фреймворком — легко заблудиться в настройках моделей. Альтернативное решение — использование репозиториев, где запрос указывается в чистом виде, а на выходе дается заранее подготовленный объект.
Абстракция от БД. Этот аргумент используют, подразумевая независимость кода от используемой базы данных. Например, во время разработки ты можешь пользоваться SQLite, а на production подключиться к Postgres и оставить код в том же состоянии. Либо при росте проекта перейти к более профессиональному решению, когда SQLite покажется недостаточно функциональным. У меня был опыт п��рехода рабочего проекта с SQLite на Postgres и это оказалось совсем непросто. Есть большая вероятность конфликта типов и ваш ORM не всегда сможет это объяснить в своих Exception так, чтобы вы поняли с первого раза.
Проверка типов. Когда мы описываем модель, то в коде прописываем типы. ORM валидирует данные на этапе компиляции или интерпретации, сообщает нам об ошибке в случае несоответствия. Но у базы данных уже встроены типы и мы так или иначе можем получить ошибку во время вставки. Драйвер базы данных часто сообщает об этом, нужно лишь поймать Exception. При этом, например в Laravel, валидацию принято внедрять на уровне контроллера.
Миграции схемы БД. На самом деле, миграции имеют определенную ценность в командной разработке, хотя часто работа с миграциями может нести в себе скрытые проблемы. Например, у меня возникали проблемы с переименованием колонок в Django. Механизм генерации миграций устроен так, что он парсит модель и на ее основе генерирует код, который затем выполняется в базе данных. Этот код не всегда соответствует задуманному решению. Даже ручное изменение базы данных можно сделать понятнее и быстрее.
Кеширование не так уж и сложно для понимания. Если оно необходимо, всегда можно настроить кеширование конкретного запроса, либо обернуть вызовы по требованию.
Ну а когда речь заходит об агрегатных функциях или сложных запросах с использованием вложенности, условий или других вспомогательных функций движка базы данных, то всё становится совсем плохо. Если использование JOIN еще воспринимается нормально после нескольких часов практики, то сложные надстройки, требующие нетривиальных запросов, выносят мозг. Каждая ORM реализует эти возможности по-своему.
Каждый раз, когда приходится работать с ORM, при переходе от одного фреймворка к другому задаешься вопросом - “а зачем все это нужно, когда с SQL работать в разы проще и понятнее?”. Стоит однажды разобраться с SQL, используя популярные запросы, и моментально получаешь ключ к данным, отбрасывая необходимость в велосипедах на ORM.
Давайте я проиллюстрирую это на одном конкретном примере: представим, что нам нужно выбрать всех пользователей, у которых есть хотя бы один завершенный заказ, и посчитать количество их активных заказов.
Посмотрим, как эта задача решается на разных инструментах.
Примеры использования ORM
Для начала, рассмотрим Laravel с его Eloquent ORM. Наш запрос можно описать несколькими способами, используя ORM или Query Builder подход:
Вариант 1. Использование Laravel Eloquent ORM
$users = User::whereHas('orders', function($query) { |
Для выполнения этого кода необходимо, чтобы были объявлены отношения между таблицами в моделях.
А вот так мы, используя Query builder, будем строить наш запрос в том случае, если отношения не определены:
Вариант 2. Использование Query Builder Laravel - почти SQL
В целом, выглядит не плохо.
Теперь попробуем использовать другой популярный фреймворк и посмотрим, что есть у Django:
Пример запроса в Django ORM с применением аннотаций
from django.db.models import Count, Q |
А вот другой вариант, с агрегацией - чуть сложней:
from django.db.models import Count, Q |
Вроде выглядит неплохо, когда этот запрос уже написан и протестирован — действительно. Когда ты пишешь такие запросы ежедневно — возможно. Но мне к сожалению, это дается нелегко.
А вот и главная боль — использование Битрикс ORM:
$users = UserTable::query() |
В Битрикс есть несколько способов для построения запросов, но все они у меня вызывают нервный тик, особенно во время их написания: чтобы просто присоединить таблицу, тебе нужно объявлять сущности, runtime-поля, ReferenceField'ы... Код “распухает” до неузнаваемости, и на третьем экране ты уже забываешь, что вообще хотел сделать. Приходится разбираться с каждой абстракцией, заложенной разработчиками фреймворка.

Чистый SQL запрос
А теперь, я хочу показать, что делают все вышеописанные конструкции на самом деле и как это могло бы быть, если бы я знал базовый SQL до того, как стал возиться с этими ORM и Query Builder. Нативный, продуманный до мелочей, лаконичный синтаксис SQL:
Выполняем его в raw конструкции и получаем те данные, которые ожидаем. Моментально избавляем себя от необходимости проводить часы погружения в документацию к любимому фреймворку. После ORM монстров, этот код кажется "глотком свежего воздуха". Один раз поняв суть построения запросов, ты можешь применить это знание в любом проекте, на любом языке. Как человеку, который работает с PHP, Python, JS, Go, мне это невероятно важно.
Понимание того, как писать запросы, приходит после прохождения базового курса по SQL запросам. Будет проще, если вы найдете интерактивный тренажер по SQL, что ускорит обучение.

Инструментарий
Любопытно, что, начав активно пользоваться SQL, я стал гораздо лучше понимать, где и с какими данными я работаю. Я использую DBeaver для просмотра таблиц: там же пишу запрос, сразу вижу его результат и при необходимости оптимизирую. Интерфейс программы дружелюбен и заточен именно под работу с базами. Кроме того, полезные сложные запросы можно сохранять и использовать повторно.
Эта же программа позволяет обходиться без консоли при импорте и экспорте данных — можно выгружать результаты даже в CSV. Не знаю, как для кого, а для меня это стало настоящим открытием и лайфхаком. Вероятно, этому где-то учат, но мне пришлось провести немало дней за тупыми скриптами для выгрузки в CSV, не имея полной картины данных и пытаясь ориентироваться лишь на определение моделей в коде.
Надеюсь, мой опыт будет полезен новичкам и поможет им не тратить время на бесконечное изучение абстракций: сразу начать работать с мощными и понятными инструментами, выполняя задачи быстрее и эффективнее.
Вывод
ORM - хороший инструмент для простых CRUD операций. Но для сложных запросов и работы с большими объемами данных, чистый SQL оказывается более мощным, понятным и предсказуемым.
Инвестируйте время в изучение SQL — это окупится многократно в любом проекте, на любом стеке технологий.
Используйте удобные и эффективные инструменты для работы с данными, такие как Dbeaver, чтобы видеть и понимать свои данные напрямую.
Ну, а если у Вас есть аргументы посильнее, приглашаю поспорить в комментариях.

