Обновить

Мой 14-летний путь отказа от ORM: череда инсайтов, приведшая к созданию SQL-First кодогенератора для PostgreSQL

Уровень сложностиПростой
Время на прочтение4 мин
Охват и читатели9.5K
Всего голосов 19: ↑18 и ↓1+22
Комментарии15

Комментарии 15

Если сделаете ещё один шаг то сможете прийти к идее компонентов доступа к БД которые во времена Delphi/ Interbase активно испольщовались и позволяли использовать логику на уровне БД хранимые процедуры.

Как вариант борьбы с рассинхронизацией схемы и запросов в БД давно придумали views.

Которые позволяют адаптировать изменения под «старые» требования

Согласен с вашим ходом мысли. Как хранимые процедуры, так и View предоставляют возможность обеспечивать обратную совместимость запросов при миграции БД. На эту тему был интересный доклад от Макса Грамина на PGConf в этом году (https://pgconf.ru/talk/3033632).

Однако в обоих случаях остаётся открытым вопрос интеграции со стороны языка. Ни View, ни хранимые процедуры не дают статических проверок, интеграционного кода и тестов в языке программирования: это остаётся на плечах программиста.

в итоге понял, что самой базе данных и должна принадлежать роль единственного источника истины

А я примерно с этого же года использовал БД как источник истины и пришёл к тому, что он должен быть не там

Непонятно, чем model-first лучше чем code-first: определив типы и их отношения, мы как раз и описываем модель. В том же ef core есть куча точек расширений, в которых можно получить доступ к этой модели и сгенерировать на ее основе всё что угодно.

Спасибо за ссылку. Интересная статья. Ироничная подача - тоже класс!

Насколько я понял, вы предлагаете отказаться от дихотомии: DB-first/Code-first, - и ввести третье представление между БД и языками. Данное представление будет нейтральным как относительно языков, так и относительно БД. Ценность этой нейтральности ясна: гибкость и устойчивость этой модели к переходам как между языками, так и между БД. Но помимо ценности есть и цена: переход сложности задачи от 2х до 3х тел со всеми вытекающими.

На мой взгляд, предлагаемый вами подход корректен и более гибок, чем DB-First, но и сложнее. Когда гибкость в отношении выбора БД не является требованием к системе, эта сложность будет избыточной. Иными словами, какой подход выбирать, как всегда в инженерии, "зависит".

Не думаю, что сложнее. Этот подход похож на DB-First тем, что правится база данных и потом генерируется то, что вам там надо по схеме. Отличие только в одном. DB-First ломается на нескольких окружениях, когда есть локальная БД и всякие /dev/stage/prod. У каждой БД будет своя истина, а в model-first база данных подгоняется под модель механизмом синхронизации. Code-first решает эту же проблему путём выкладывания в спец. таблицу истории миграций и жёстким запретом на трогание БД руками. Что очень неудобно.

И кстати. Если вы генерите sql запросы по схеме, можете попробовать скооперировать с моим инструментом. Его задача - выдать вашему скрипту актуальное состояние схемы данных и для абсолютной гибкости подхода я и сделал скрипт на стороне пользователя тулзы.

DB-First ломается на нескольких окружениях, когда есть локальная БД и всякие /dev/stage/prod. У каждой БД будет своя истина, а в model-first база данных подгоняется под модель механизмом синхронизации. Code-first решает эту же проблему путём выкладывания в спец. таблицу истории миграций и жёстким запретом на трогание БД руками.

А как же инструменты миграции Flyway, Liquibase, Sqitch и тп? Там так же накатывание миграций автоматизируется за счёт спец-таблиц. Конкретно это вовсе не прерогатива code-first.

Если вы генерите sql запросы по схеме

Их пишет пользователь.

Когда я только начинал и увидел hibernate, думал, что лучше его не будет, но часто именно такая магия, абстракция, становится узким местом и болью, особенно, если БД это основа приложения.

На одном из проектов я познакомился с JOOQ, DSL для работы с базой данных и насколько же удобно и прозрачно все это, теперь я жесткий фатан JOOQ) Понятно, что если у вас маленький проект ORM упростит работу, но с ростом будет сложнее с ним работать

Спасибо за статью)

Спасибо! Да. jOOQ в том же лагере инструментов. Если интересно, в доках есть сравнение pGenie vs jOOQ.

А смотрели sqlc ? Goorm? Они тоже могут генерить

Про Gorm не скажу, это похоже не ниша SQL-First.

А sqlc - прямой конкурент.

Принципиальное отличие в том, что sqlc целится на разные БД, из-за чего вынужден приводить внутренние представления к пересечению множества их возможностей, отбрасывая особенности. Поэтому, например, он до сих пор не поддерживает композитные типы Postgres. pGenie сфокусирован на Postgres и уже поддерживает продвинутые типы, включая композиты, многомерные массивы, multirange.

Ещё большое отличие в подходе к анализу. pGenie опирается на сам Postgres, разворачивая временный контейнер, проигрывая в нём миграции и прогоняя каждый запрос с извлечением метаданных из информационных схем. Благодаря этому поддерживаются любые синтаксические ухищрения и запросы любой сложности. Версию Postgres пользователь может выбирать, добиваясь соответствия продовой среде. В sqlc самописный эмулятор, который породил бесконечный поток багов несоответствия поведению Postgres. В последнее время, насколько я понял, они от него из-за этого стали плавно уходить.

Подробное сравнение есть в документации.

Здорово, спасибо

У нас .net ef + hot chocolate graphql. И много sql вообще под капотом собирается. Например, фильтры для админок склеиваются с фильтрами для контроля доступа, сверху докидываются сортировки, и т.п. Понятно, что в этом случае контроль над sql вообще утерян, но нам вроде и не надо - для всяких табличек в админах особенно.

В твоем подходе какая-то композиция запросов предполагается? Или только средствами БД (вьюшки и т.п.)?

Если подразумевается динамическое построение запросов, то пока не предполагается.

В демо-проекте есть пример со статическим запросом, реализующим динамическую выборку фильтров, условий сортировки и выборки. Для примера, вот генерируемый из него код Java.

Вопрос немного из другого разреза разработки - а зачем вообще пытаться что-то рефлектить "из SQL", или "из языка"?

В чем проблема использовать БД как фронт использует бэк, делая запросы к хранимкам (если уж там есть сложная логика)? В таком случае при миграциях приложение вообще трогать не нужно будет, если старый вид данных, конечно, можно как-то получить. Достаточно изменить хранимку в БД - т.е. при bзменении БД вы остаетесь в парадигме лишь этой БД.

Что касается тестов - естественно, они внешние должны быть, тут ничего не сделать, но эндпоинты на хранимках избавляют от необходимости переделывать те тесты, которые уже были для них написаны, и нужно просто написать тесты только для нового или изменившегося функционала.

Вообще, использовать рефлекты при работы с БД - это очень заманчивая, но очень порочная практика, как и ORM, ИМХО. Это ускоряет старт, но жутко мешает потом в жизненном цикле. Обычные запросы и более понятны (не надо гадать, во что они там трансформируются промежуточным слоем) и легче могут быть оптимизированы или вообще заменены. Единственная проблема - смена БД. Но это крайне редкая проблема, можно сделать миграцию и ручками раз в 10 лет.

Ну, либо я не понял сути вопроса.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации