Реализация SQL API аналогична GraphQL

Итак, они говорят, что REST мертв и неэффективен. Давайте начнем революцию и изобретем новый язык запросов для вашего API. Если вы следите за технологическими трендами, вы не могли не заметить новую технологию под названием GraphQL от Facebook.

Для тех, кому интересно, что такое GraphQL, вот описание с их сайта: это язык запросов для API и среда выполнения для выполнения этих запросов с вашими существующими данными. GraphQL предоставляет полное и понятное описание данных в вашем API, дает клиентам возможность запрашивать именно то, что им нужно, и не более того, упрощает развитие API с течением времени и предоставляет мощные инструменты разработчика.

GraphQL невероятно мощный - настолько мощный, что он используется такими корпорациями, как Facebook, для управления своими большими и сложными социальными системами для миллиардов пользователей.

На самом деле, он настолько мощный, что требует «нового мышления» и множества зависимостей.

А теперь остановитесь и подумайте: вы создаете сложные системы для миллиардов пользователей? Если ответ положительный, вы, вероятно, можете перестать читать, но я чувствую, что большинство из вас скажет нет. Мне нравится простота (а кому нет), и мой опыт показывает, что если что-то работает для большой и сложной корпорации, такой как Facebook, Остальным из нас, вероятно, следует поступить наоборот.

GraphQL - это не фреймворк для запросов к данным, это патч для исправления крупных организационных и технических проблем. Та же самая история стоит за шаблоном микросервисов, и теперь посмотрите, как настроения толпы изменились с «давай воспользуемся им для всего» на «черт возьми, лучше я буду придерживаться своего монолитного стека LAMP».

Итак, предположим, что вы компания среднего размера и хотите, чтобы GraphQL был эффективным, гибким и безопасным способом обмена данными внутри и за пределами вашей организации. Давайте откроем сайт GraphQL и узнаем, как он себя позиционирует: «получите много ресурсов за один запрос», «спросите, что вам нужно», «опишите, что возможно с системой типов» и т. Д. Подождите, я только что прочитал описание старый добрый SQL? Как обычно в мире информационных технологий, ответы на новые вызовы можно найти в нашем прошлом.

Поднимите руку, если вы предпочитаете REST или GraphQL API вместо доступа к SQL, который предложит вам максимальную гибкость, хорошо известный мощный язык 30-летней давности и множество существующих библиотек? А теперь представьте, насколько ваши клиенты будут счастливы, если они смогут направить ваш API напрямую во внутренние отчеты или информационные панели, такие как charts.io?

Я не вижу слишком много рук.

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

Для простоты я выбрал PostgreSQL, потому что это лучшая база данных общего назначения SQL, которую вы можете найти; он достаточно гибкий и мощный, чтобы его можно было настроить практически для любого случая использования.

Ограничение доступа к данным

Каждый потребитель данных, служба внутри вашей организации или просто ваш клиент должны видеть только те данные, к которым у него есть доступ. PostgreSQL имеет мощную систему безопасности на основе ролей и даже позволяет указывать доступ для каждой строки.

Начнем с определения ролей:

-- Note that it is the same as CREATE USER
CREATE ROLE <user>;
-- Each role can have own password
ALTER USER <user> WITH PASSWORD '<pass>';
-- Allow to connect our database
GRANT CONNECT ON DATABASE <db> TO <user>;
-- Allow to see the tables in `public` schema
-- You may want to create multiple schemas for various use cases
GRANT USAGE ON SCHEMA public TO <user>;

Когда мы говорим о безопасности, лучше всегда заносить вещи в белый список, чем в черный. К счастью, по умолчанию новые роли не имеют доступа ни к чему, поэтому давайте добавим несколько ролей безопасности:

-- For tables that can only be read
GRANT SELECT ON <table> IN SCHEMA public TO <user>;
-- For full read-write support
GRANT SELECT, INSERT, UPDATE, DELETE ON <table> IN SCHEMA public TO <user>;

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

PostgreSQL, начиная с версии 9.5, добавил поддержку политик безопасности строк, что означает, что у вас может быть большая глобальная таблица, и отдельные пользователи будут видеть и обновлять только данные, разрешенные политикой.

Предположим, у вас есть таблица accounts, содержащая данные для входа клиента, и таблица data с фактическими данными, в которой есть столбец account_id. Также мы определим несколько ролей Postgres для каждого account_id (имя роли совпадает с именем id). current_role - это специальная функция SQL, которая возвращает текущего пользователя, вошедшего в систему.

-- First we need to enable this feature
ALTER TABLE <table> ENABLE ROW LEVEL SECURITY;
-- Allow to select only rows which user should have access to
CREATE POLICY per_user_accounts ON data FOR SELECT
  USING (account_id = current_role);
-- Allow updating only rows assigned to our account_id
CREATE POLICY per_user_accounts ON data FOR UPDATE
  USING (account_id = current_role);

Вы можете использовать более сложные проверки безопасности, фактически USING выражение может содержать почти любой оператор SQL (кроме агрегатных или оконных функций).

Использование функций для изменения данных

В реальном мире разрешение потребителям API напрямую UPDATE или INSERT может быть плохой идеей, особенно если это имеет некоторую бизнес-логику. И, возможно, имеет смысл использовать для этого серверную логику. Но на самом деле мы можем сделать это и в базе данных.

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

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

CREATE FUNCTION update_data (ts timestamp, val integer)
  RETURNS void
AS $$
  # Assume that updating data available only to enterprise customers
  r = plpy.execute("SELECT is_enterprise FROM accounts WHERE account_id = %d" % current_role, 1)[0]
  if r['is_enterprise'] == True:
    plan = plpy.prepare("UPDATE data SET val = $1 WHERE ts = $2", ["integer", "timestamp"])
    plpy.execute(plan, [val, ts]
  else:
    plpy.info("Unauthorized access")
$$ LANGUAGE plpythonu;

Python? Да! PostgreSQL позволяет использовать различные языки для написания процедур, и хотя я лично предпочитаю собственный диалект PLSQL, использование Java, R или Python вполне нормально. Хотите добавить ограничение скорости или аудит, не проблема!

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

Работа с медленными запросами

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

Приведенный ниже код автоматически прервет все операторы длительностью более 1 секунды, выданные ‹user›.

ALTER ROLE <user> SET statement_timeout=10000;

Приоритет памяти

PostgreSQL действительно имеет некоторые настраиваемые параметры для использования памяти для каждого клиента, в частности work_mem и maintenance_work_mem. Вы можете установить для них консервативные низкие значения в postgresql.conf, а затем использовать команду SET, чтобы назначить им более высокие значения для конкретной серверной части, например SET work_mem = '100MB';.

Вы можете установить разные значения для work_mem и maintenance_work_mem, используя переменные GUC для каждого пользователя. Например:

ALTER USER <user> SET work_mem = '50MB';

Как мне получить к нему доступ из Интернета

Хорошо, есть шанс, что я убедил вас, что предоставление SQL для серверных приложений может быть хорошей идеей, но как насчет публикации его в Интернете?

Давайте сделаем это так же, как GraphQL; мы создадим простую /sql конечную точку, которая, по-видимому, принимает операторы SQL.

Перед выполнением запроса на стороне конечной точки вам просто нужно правильно установить ROLE

SET ROLE <account_id>

После установки роли все политики безопасности будут автоматически применены к выполняющемуся запросу.

Генерация SQL и анализ ответа на стороне клиента не должны быть проблемой, оставим это на усмотрение читателя.

SQL настолько сложен

SQL похож на Excel, он настолько распространен, что даже большинство менеджеров знают основы и могут написать простой запрос SELECT * и передать его в charts.io или аналогичную панель управления. Кроме того, в большинстве существующих инструментов есть автоматическое обнаружение столбцов и предложения, или даже визуальные построители запросов.

Вы можете сказать, что не можете научить своего фронтенд-инженера писать JOINS, не говоря уже о функциях WINDOW или эффективном использовании индекса. И этого делать, собственно, и не нужно!

SQL позволяет создавать ВИДЫ, разновидности черных ящиков. Вы можете определить запрос любой сложности и просто назначить его представлению. Для конечного пользователя он будет действовать как стол.

Везде, где вам нужна настраиваемая логика, например функции агрегирования или объединения, просто создайте новое представление! Вот как это просто. Более того, вы можете ограничить доступ пользователей только к представлениям, не открывая доступ к исходным таблицам.

Запросы из нескольких источников данных

GraphQL может скрыть сложную логику за запросом, и вы можете смешивать данные из нескольких источников данных.

Это вопрос архитектуры, которую вы создаете, и в большинстве случаев, когда вы запрашиваете данные из стороннего API, вы, вероятно, где-то кешируете их, так почему бы не в базу данных? Вам также следует подумать о том, что для вас более затратно: размещение всех данных в единой базе данных или наличие десятков нескольких баз данных. Все зависит от вас, но позвольте мне рассказать вам историю о моем последнем проекте, по нему решаю эту проблему, используя единственную базу данных.

Итак, мне нужно собрать информацию из нескольких источников API, таких как Github, Clearbit, проверка электронной почты и т. Д., Смешать ее и предоставить пользователю. Запросы к сторонним сервисам по запросу пользователя почти всегда плохая идея, слишком высока вероятность того, что что-то пойдет не так. Вероятно, мы хотим кэшировать данные, периодически обновлять их и показывать пользователю только кешированную версию.

У меня есть работник, который периодически извлекает необработанные данные из этого API и помещает их в базу данных. Затем есть представления, которые извлекают данные из JSON (да, вы можете запрашивать JSON в Postgres!), И функции, которые его нормализуют:

CREATE VIEW github_meta_ex AS
SELECT github_meta.login,
       format_company(payload ->> 'company') as company,
       (payload ->> 'followers')::numeric as followers
       ...
FROM github_meta
-- And similar views for each service

После получения данных у меня есть final MATERIALIZED VIEW, который объединяет все данные вместе. Материализация в этом случае означает, что представление сделает снимок запрошенных данных и создаст временную таблицу для их хранения. Независимо от того, насколько сложны ваши запросы и сколько обработки JSON вы выполняете, для конечного пользователя это будет так же быстро, как простая статическая таблица.

CREATE MATERIALIZED VIEW meta_ex AS
SELECT g.login,
 g.email,
 COALESCE(g.company, c.employment) AS company
 ...
FROM FROM github_meta_ex as g
LEFT JOIN clearbit_meta_ex as c ON g.email = c.email
...

И периодически, обычно, когда вы обновляете одну из зависимых таблиц, вы просто вызываете REFRESH MATERIALIZED VIEW meta_ex, который в фоновом режиме обновляет данные представления, не блокируя пользователя.

Заключительные слова

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

Современный SQL может дать вам достаточно гибкости для создания безопасного и эффективного доступа к вашим данным, и вы можете реализовать его ПРЯМО СЕЙЧАС, не решая, следует ли вам переписать половину вашего приложения с нуля для поддержки «современных» технологий (что, скорее всего, это просто мост к чему-то новому, что будет в ближайшие годы).

Я лично с удовольствием использую такой SQL API в производстве, и мои клиенты очень довольны, он работает для обеих сторон.

Понравилось это прочитанное? Нажмите ❤ ниже, чтобы порекомендовать его другим заинтересованным читателям!

PS. Кстати, я начал небольшой концерт, чтобы помочь людям с интеллектуальным анализом данных и SQL в частности http://sqlexperthelp.com