Objection.js — минималистичная ORM-библиотека для Node.js, которая сильно упрощает взаимодействие с базами данных и не перегружена дополнительными функциями, как sequalize или typeORM. Разбираемся, в чем ее специфика и как строить запросы с ее помощью.
Это адаптированный перевод статьи A Step Towards Simplified Querying in NodeJS из блога компании Velotio Technologies. Повествование ведется от лица автора оригинала.
Недавно я увидел на StackOverflow историю разработчика, который столкнулся с проблемой использования ORM. Этот кейс натолкнул меня на мысль написать статью о альтернативе sequelize — Objection.js, библиотеке, которая решает множество проблем ORM.
Что такое ORM
ORM или метод реляционного сопоставления объектов — решениe для сопоставления объектов в программе с таблицами базы данных. Он позволяет разработчикам взаимодействовать с объектами вместо того, чтобы писать запросы к базе данных.
ORM позволяет строить динамические запросы с помощью Query Builder и абстрагироваться от особенностей конкретной базы данных. Решения обычно поддерживают Postgres, MySQL, SQLite и другие базы данных.
Вопрос на StackOverflow касался преобразования приведенного ниже запроса в sequelize-запрос. Стоит отметить, что его писал начинающий разработчик:
Решение на sequelize выглядит так:
Учитывая простоту запроса, решение кажется сложным, и чем больше в нем новых отношений, тем оно сложнее. Такие простые запросы в sequelize описаны недостаточно подробно.
Для сравнения разберем, как решение задачи будет выглядеть на Objection.js:
Буду честен — я сам пользуюсь Objection.js, что не делает его лучшим ORM для NodeJS. Каждая библиотека имеет свою специфику, а некоторые разработчики вообще выступают против любых ORM и предлагают вручную писать SQL-запросы. Отчасти они правы. Если ваше приложение достаточно маленькое, и вы можете написать несколько функций-хелперов для выполнения запросов, то используйте обычный SQL вместо ORM.
ORM нужна, когда в приложении много таблиц с большим количеством связей, которые нужно определить, и выполнить несколько объединенных запросов.
Список самых упоминаемых ORM для NodeJS выглядит так:
Почему я выбрал Objection.js
В основе решения — мощный конструктор SQL-запросов KNEX.JS.
Библиотека позволяет выполнять запросы через функции async/await.
Позволяет добавить валидацию с помощью JSON схемы.
Позволяет создавать модели для таблиц с использованием синтаксиса ES6/ES7 и определять связи между ними.
Objection.js автоматически строит связи между объектами и самостоятельно определяет, какие данные нужно сохранить, а какие — обновить (за это отвечают функции
upsert
иinsert
).
У Objection.js и KNEX.JS очень понятная и обширная документация, в которой легко найти инструкции и примеры использования. В официальном репозитории библиотеки на GitHub есть множество примеров использования библиотеки и подробная инструкция по ее установке и настройке.
Создание и управление схемой базы данных
Миграция — один из подходов к внесению изменений в базу данных, в этой статье сосредоточимся на нем. В Objection.js используются механизмы миграции KNEX.JS. Поскольку речь идет об ORM, определять таблицы и столбцы можно не с помощью SQL, а непосредственно написанием кода на JavaScript. Для создания новой миграции выполним команду KNEX CLI:
После выполнения этой команды в каталоге миграций появится новый файл с текущей временной меткой и именем, которое мы присвоили этой миграции. Выглядит он следующим образом:
Функция exports.up
определяет команды, выполнение которых внесет нужны изменения в базу данных. Например, речь может идти о создании таблиц базы данных, добавлении или удалении столбца из таблицы, изменении индексов и других действиях.
Вторая функция, exports.down
, выполняет противоположные первой действия: например, когда exports.up
создает таблицу, exports.down удаляет ее. Она нужна для того, чтобы быстро откатить изменения, сделанные миграцией, если это потребуется.
В случае ниже в функции up
создается таблица persons
с необходимыми столбцами и индексами, а в функции down
таблица удаляется, если она существует:
Запустить процесс миграции можно следующим образом:
Также можно передать флаг — env
или установить NODE_ENV
, чтобы выбрать альтернативную среду:
Модели
Модели базы данных — абстракции вокруг таблиц базы данных, которые помогают инкапсулировать бизнес-логику внутри них. В Objection.js модели можно создавать c помощью классов ES.
Прежде чем продолжить, уточним, что модели в Objection.js не создают таблиц в базах данных, а предназначены для добавления валидаций и создания связей между моделями. Например:
В этом коде есть три статистических геттера. Первый, tableName
, возвращает имя таблицы.
Второй, jsonSchema
, определяет параметры валидации каждого поля. В нем можно указать необходимые свойства: например, тип поля (число, строку, объект и тд).
Третий, relationMappings
, определяет связь выбранной модели с другими моделями. В данном случае ключ внешнего объекта pets
— ссылка на связанный класс. Свойство join
определяет, как модели связаны друг с другом, а from
и to
объекта join
определяют поля базы данных, с помощью которых связаны модели. Класс ModelClass является классом связанной модели.
Таким образом, класс Person
имеет связь HasManyRelation
(один ко многим) с моделью Animal
. Модели связаны через столбцы persons.id
и animals.OwneId
. В результате мы видим, что у одного человека может быть несколько домашних животных.
Запросы
Начнем с простого запроса SELECT:
Простой SELECT будет выглядеть на Objection.js следующим образом.:
Запрос выше затрагивал все поля в таблице, а так можно реализовать запрос с выбором одного поля:
Из примеров выше видно, насколько запросы в Objection.js похожи на SQL-запросы. Преобразовать одни запросы в другие довольно просто, что нетипично для ORM.
Запросы создания и вставки
Другой способ его выполнения выглядит так:
Запросы удаления
Другой способ его выполнения выглядит так:
Запросы по отношениям
Предположим, мы хотим получить данные обо всех домашних животных пользователя по имени Бен:
Теперь предположим, что вы хотите добавить информацию о пользователе со всеми его животными. Для этого можно использовать запрос с помощью графа:
Доступные плагины
objection-password — плагин, который позволяет автоматически хэшировать пароли для модели Objection.js. Это легкий способ защитить пароли и другие конфиденциальные данные.
objection-graphql — автоматически генерирует GraphQL API для модели Objection.js.
Заключение
В заключение стоит отметить, что если передо мной встанет выбор, какую ORM использовать, я выберу Objection.js. В первую очередь потому, что запросы в ней выполняются проще, чем в sequalize.
В целом Objection.js не похоже на другие ORM. По сути, это просто оболочка поверх конструктора запросов KNEX.js, поэтому лучше всего воспринимать решение как конструктор запросов с дополнительными функциями.
Комментарии (10)
romadomma
12.04.2022 11:57сравнительно молодая и минималистичная ORM-библиотека для Node.js
7 лет назад первые публикации пакета появились в npm, не такая уж и молодая. На год старше того же TypeOrm. Как раз на проекте исторически сложился Objection, с приходом NestJs как раз заглядываюсь на TypeOrm.
rudinandrey
12.04.2022 12:55+5когда в приложении много таблиц с большим количеством связей, которые нужно определить, и выполнить несколько объединенных запросов
вот по моему опыту, как раз когда в приложении много данных, по разным таблицам, и есть ложные связи, вот именно тогда лучше всего самим писать SQL запросы, потому что, что там нагенерирует эта ORM большой большой вопрос.
feycot
12.04.2022 15:50Ну нужно понимать, что нет серебрянной пули. Если запросы, создаваемые ORM будут мешать жить, то можно залезть и отпрофилировать и заменить на нативные запросы. Другое дело, что во время разработки зачастую важна скорость создания фичи, а оптимизацией можно заняться потом (то есть никогда :D)
denbon05
12.04.2022 14:03А не лучше наоборот, если приложение большое, запросы сложные и много данных, не использовать orm? Получается мы тянем в js код промежуточные данные?
feycot
12.04.2022 16:54А чем лучше?
denbon05
12.04.2022 22:12В плане производительности. К примеру мы соединяем две таблицы, при этом орм тянет в код данные из двух таблиц и потом по условию фильтрует нужные, в то время, как сырой sql запрос использует планировщик и все операции происходят на уровне бд
Suvitruf
12.04.2022 23:35ORM драйверу БД скармливает сформированный запрос точно такой же, как если бы вы писали его на голом SQL. Если работать с ORM правильно, то никаких лишних данных не тянется. Там есть оверхед, да, но он на уровне структур и обработки ответа.
Schekotka
Кажется пропущен пример JS для запроса:
SELECT * FROM persons WHERE firstName = 'Ben' ORDER BY age;
В оригинале он такой:
OlegSabitov Автор
Спасибо, действительно упустили!