Biff.graph: превратите кодовую базу на Clojure в граф, к которому можно делать запросы

Biff.graph: превратите кодовую базу на Clojure в граф, к которому можно делать запросы

Джейкоб Брайант, автор веб-фреймворка Biff для языка Clojure (на Hacker News и GitHub, под ником jacobobryant), представил новую библиотеку biff.graph. Она позволяет обращаться и к базе данных, и к производным данным бизнес-логики как к единому расширяемому графу с запросами: модель данных разбивается на маленькие независимые функции («резолверы»), а прикладной код лишь декларирует форму нужных ему данных, не зная, как именно их получить. По словам автора, это делает кодовую базу проще для понимания и тестирования по мере её роста.

biff.graph описан как облегчённая версия библиотеки Pathom, де-факто стандарта для графового моделирования данных в мире Clojure. Новая библиотека реализует лишь часть возможностей Pathom, сознательно жертвуя функциональностью ради простоты: в отличие от Pathom, в biff.graph нет этапа планирования запроса, из-за чего сложные запросы могут выполняться менее эффективно (хотя пакетная обработка и кэширование поддерживаются). Зато вся кодовая база biff.graph, около 600 строк, из них на движок выполнения запросов приходится примерно 200 строк.

Мотивация, по словам автора: он давно и с удовольствием использует Pathom и на работе, и в личных проектах, но сомневался, стоит ли включать её в Biff по умолчанию, для людей, которые никогда не слышали о Pathom и делают небольшие проекты, выгода от такой структуры кода не всегда оправдывает усилия на изучение. biff.graph, попытка проверить, насколько доступным можно сделать этот паттерн: пользователям Biff всё равно придётся освоить те же концепции, что и для Pathom, но благодаря компактности кода следующий шаг, разобраться, что именно делает библиотека при отладке, должен даваться проще.

Технически резолвер, это функция с объявленными входным и выходным запросами: она принимает данные в форме входного запроса и возвращает данные в форме выходного. Форма данных описывается модифицированным подмножеством синтаксиса EQL и шаблонов выборки (pull patterns) из Datomic. После того как определён набор резолверов, движок запросов biff.graph использует их, чтобы вернуть данные в любой запрошенной форме, не заботясь о том, откуда берётся каждый атрибут, из базы или как вычисленное значение. В примере из документации это показано на трёх резолверах: два имитируют доступ к базе данных (получение RSS-ленты и поста по первичному ключу), третий, производный резолвер, который берёт заголовок поста и возвращает его версию без эмодзи; итоговый запрос идёт через единую функцию com.biffweb.graph/query, не различая в синтаксисе поля из базы и вычисляемые.

Для реальных проектов автор советует сначала автоматически генерировать резолверы прямо из схемы базы данных: один резолвер на таблицу (тип сущности), входной запрос, только первичный ключ, выходной, все остальные колонки плюс ключи связи (join) для внешних ключей, со значением в виде первичного ключа связанной сущности. Такие резолверы должны иметь опцию :batch true, чтобы одним обращением к базе получать сразу несколько сущностей: резолвер получает вектор входных карт и обязан вернуть вектор выходных карт в том же порядке, по сути выполняя SELECT * по списку первичных ключей. Ещё не выпущенная библиотека biff.sqlite уже включает такой генератор резолверов для SQLite, впрочем, автор прямо предупреждает, что этот фрагмент кода написан ИИ и не редактировался человеком. Поверх автосгенерированных резолверов можно постепенно добавлять собственные через defresolver, перенося логику из обычных вспомогательных функций.

Резолверы передаются в com.biffweb.graph/new-ctx, которая строит индексы для движка запросов и оборачивает резолверы промежуточным слоем обработки для кэширования и валидации; после этого запросы можно выполнять где угодно, например в начале обработчика запроса Ring. Библиотека добавляет и служебные возможности: исключения из com.biffweb.graph/query несут в данных ключ :biff.graph/trace с указанием, в какой точке обхода графа и на каком резолвере произошёл сбой, это помогает свести проблему к минимальному воспроизводимому случаю. При использовании эффект-библиотеки biff.fx резолверы можно писать в виде конечных автоматов этой библиотеки, тогда обращения к базе или внешним сервисам делаются прямо внутри резолвера, а сам com.biffweb.graph/query доступен через ключ :biff.graph.fx/query. Если проект регистрирует схему данных через com.biffweb.core/register, biff.graph проверяет соответствие вывода резолверов этой схеме. Отдельно автор описывает паттерн авторизации: «params»-резолверы берут идентификатор сущности из параметров пути или запроса, проверяют права текущего пользователя и возвращают сущность как связь, либо бросая исключение при отказе, либо просто не включая сущность в вывод; единого способа превратить оба варианта в HTTP-ответ 4XX автор пока не выбрал и называет это предметом дальнейших экспериментов.

На момент публикации библиотека распространяется как релиз-кандидат, зависимость com.biffweb/graph версии 2.0.0-rc7, и сохранит этот статус, пока не выйдут остальные библиотеки экосистемы Biff 2; автор не исключает обратно несовместимых изменений до финального релиза, хотя не планирует их вносить намеренно. Пост на Hacker News набрал 129 очков и 16 комментариев.

Ключевые факты

  • Джейкоб Брайант (jacobobryant), автор Clojure-фреймворка Biff, выпустил библиотеку biff.graph, облегчённую альтернативу Pathom для запросов к базе данных и к производным данным бизнес-логики как к единому графу.
  • Вся кодовая база biff.graph, около 600 строк (движок запросов, около 200 строк); в отличие от Pathom, здесь нет этапа планирования запроса, поэтому сложные запросы могут выполняться менее эффективно, зато поддерживаются пакетная обработка и кэширование.
  • Модель данных строится через резолверы, функции с входными и выходными запросами в модифицированном синтаксисе EQL/Datomic pull patterns; для типовых таблиц автор рекомендует автогенерировать резолверы прямо из схемы БД (готовый генератор уже есть в ещё не выпущенной biff.sqlite для SQLite).
  • Библиотека даёт отладочную трассировку ошибок (:biff.graph/trace), интеграцию с эффект-библиотекой biff.fx и паттерн авторизации через «params»-резолверы, которые перед возвратом сущности проверяют права доступа текущего пользователя.
  • На момент публикации biff.graph, релиз-кандидат (com.biffweb/graph 2.0.0-rc7), который станет стабильным только после выхода остальных библиотек Biff 2; пост на Hacker News набрал 129 очков и 16 комментариев.

Почему это важно

В экосистеме Clojure для графового моделирования данных, единого доступа к базе данных и к вычисляемым значениям как к дереву атрибутов, де-факто стандартом стала библиотека Pathom, но она сложна для новичков и добавляет заметный порог входа. Автор фреймворка Biff столкнулся с конкретной дилеммой: включать ли Pathom в Biff по умолчанию, рискуя отпугнуть разработчиков небольших проектов сложностью, или отказаться от преимуществ графового подхода к данным. biff.graph, его ответ: минималистичная реализация того же паттерна примерно в 600 строк кода, сознательно жертвующая частью производительности и функциональности ради простоты и прозрачности при отладке.

Кому это важно

В первую очередь, разработчикам на Clojure, использующим веб-фреймворк Biff, которым раньше для графового моделирования данных приходилось либо подключать полноценный Pathom, либо вручную писать связывающий код между базой данных и бизнес-логикой. Также релевантно тем, кто присматривается к паттерну «запрос к единому графу данных» (как в GraphQL или Pathom) для небольших и средних проектов, где порог входа в полноценный Pathom кажется избыточным.

Как это применить

Нужно подключить зависимость com.biffweb/graph версии 2.0.0-rc7 (пока статус, релиз-кандидат экосистемы Biff 2). Резолверы описываются через defresolver с входными и выходными запросами в синтаксисе EQL/Datomic pull patterns; для обычных таблиц базы данных автор советует генерировать резолверы автоматически, один на сущность, вход, первичный ключ, выход, остальные колонки плюс ключи связи по внешним ключам, с опцией :batch true для пакетной выборки нескольких сущностей за один запрос к базе. Набор резолверов передаётся в com.biffweb.graph/new-ctx, который добавляет индексацию, кэширование и валидацию; запросы через com.biffweb.graph/query можно вызывать из любого места, например в начале обработчика запроса Ring. Для проверки прав доступа автор предлагает паттерн «params»-резолверов, которые проверяют права пользователя перед тем, как вернуть сущность.

Можно ли доверять

Источник, официальный README к новой директории libs/graph в собственном GitHub-репозитории автора фреймворка Biff, опубликованный им самим на Hacker News (один и тот же ник jacobobryant в обоих местах). Материал не рекламный: указан точный номер версии, явно обозначен статус «релиз-кандидат», перечислены конкретные технические ограничения (нет планирования запросов, возможны обратно несовместимые изменения), а фрагмент кода для SQLite прямо помечен как неотредактированный после ИИ-генерации. Это техническая документация от первого лица, а не пересказ третьих лиц, так что оснований сомневаться в фактах нет.

Риски и подводные камни

Библиотека имеет статус релиз-кандидата, до финального релиза Biff 2 возможны обратно несовместимые изменения API. Отсутствие этапа планирования запроса означает, что сложные запросы biff.graph способен выполнять менее эффективно, чем полноценный Pathom. Смежная библиотека biff.sqlite с автогенератором резолверов для SQLite ещё не выпущена, а идущий с ней фрагмент кода прямо помечен как неотредактированный ИИ-код. Паттерн авторизации через «params»-резолверы сам автор называет экспериментальным, единого способа превращать отказ в доступе в HTTP-ответ 4XX в библиотеке пока нет.

«Я сделал biff.graph, потому что мне давно нравится использовать Pathom и на работе, и в личных проектах, но меня беспокоило включение её в Biff по умолчанию.»

— jacobobryant, автор Biff и biff.graph, в README проекта