Уйти от ORM
Сегодняшние проблемы ORM
Проблемы ORM хорошо известны — именно поэтому вы и читаете эту статью. Подробно о проблемах конкретной реализаций ORM на Java мы писали ранее. Все тезисы этой статьи распространяются и на Python с SQLAlchemy и другие ЯП. Начнем с определения ORM в контексте, например, Java.
ORM — СУБД, написанная на Java в качестве бекэнда которой является другая СУБД.
Почему так?
свой язык декларации схемы данных
свой язык запросов (Criteria Api, HQL, JPQL)
свой оптимизатор (anti N+1)
свой pl/SQL — API вокруг Entity Manager, Sessions со скриптингом на Java
Буква «О» в названии нужна по двум причинам. Во-первых потому что Java (как и многие другие) — это объектно ориентированный язык (что бы это не значило). Во-вторых — подчеркнуть размеры дыры в абстракции ØRM.
В такой постановке сразу виден корень зла ORM — вы используете сразу ДВЕ СУБД вместо одной. Например, Hibernate и PostgreSQL.
Всеобъемлющий мануал прикладного программиста (включая вопросы производительности и сам язык запросов, pl/SQL) для PostgreSQL, составляет около 700 страниц. Middle Vulgaris уверенно знает страниц 200 из них. Hibernate, как минимум, удваивает необходимый объем знаний, добавляя еще 600 страниц. При этом, широко известные проблемы с композицией ставят в тупик даже стреляных синьоров.
Разработчики молчаливо поддаются искушению дешёвых CRUD операций
Чего мы на самом деле хотим?
Сейчас мы опустим разные вопросы, типа того, что жаль что ORM фреймворки так и не научились самостоятельно решать вопросы оптимизации, композиции и сдвинули все эти проблемы на Петровича. Самое важное это нейро-моторесурс разработчика и на что его потратить. Его можно потратить на то чтобы научиться делать нормальный софт (например, научиться обрабатывать ошибки), либо запрыгнуть в паровозик Spring Data JPА.
Давайте, наконец, признаемся, что нам нужна просто хорошая библиотека для подключения к базе данных, легко решающая типовые задачи backend разработки.
Правильный API для взаимодействия с базой.
Весь нужный нам API:
connection.fetch(query, args) -> result
И это не шутка. Немного развернем идею:
Query пишется на нативном языке базы (например, SQL)
Query проверяется на этапе компиляции, типы аргументов тоже
Typeof (result) выводится автоматически, что избавляет от ручного написания DTØ. Это приводит к образованию единого типового пространства между бэкэндом и схемой базы
С помощью SQL вы по месту получаете нужные вам проекции с базы. Это позволит отказаться от некоторых сомнительных «слоев» вроде сущностей, репозиториев и «мэпперов». Ситуацию с сервисами мы описывали ранее.
SQL базы имеют некоторую специфику, вроде результата в табличной (строковой) форме и некоторых дополнительных усложнений, вроде разницы между «просто запросом» и вызовом хранимых процедур, всяких дополнительных вещей типа generated keys и update count, доступ к которым не всегда есть на уровне запроса. Все это тоже немного влияет на итоговый API.
Не торопитесь писать, что это очередная статья, которая к чему-то призывает, но не дает никаких решений! Новое безальтернативное решение появится совсем скоро.
Доктор едет-едет сквозь снежную равнину.
Порошок целебный людям он везёт.
Человек и кошка порошок тот примут,
И печаль отступит, и тоска пройдёт.
Ноль — Человек и кошка