Взгляд на RPC со стороны хранимок
Термины и определения
Во избежание разночтений, приведу определения используемых в тексте сокращений:
- Хранимка
- “Хранимая процедура или функция (stored procedure, function). Данные объекты БД пишутся на языке процедурного расширения языка SQL, который дополняет язык SQL такими управляющими структурами языка высокого уровня, как ветвления и циклы, и позволяет реализовать любые алгоритмы обработки данных. Хранимый код постоянно хранится на сервере и выполняется по запросу на его запуск из приложений клиентов” 1.
- API
- описание способов взаимодействия одной компьютерной программы с другими 2.
- RPC API
- “Удалённый вызов процедур… класс технологий, позволяющих программам вызывать функции или процедуры в другом адресном пространстве” 3.
Предисловие
Когда идею совмещения HTML и PHP в одном файле начали считать неправильной, я был этому рад, т.к. считаю, что такое совмещение:
- Требует переключать контекст между разными стеками - декларативным и функциональным.
- Размазывает требования к квалификации разработчика - уже недостаточно разбираться в одной из двух технологий.
Когда идея микросервисов начала принимать форму “каждому сервису - свою БД”, я был удивлен и опечален тем, что выводы из истории HTML+PHP, очевидные для меня, не попали в тренд.
В контексте “БД” я рассматриваю только СУБД, которые поддерживают хранимый код, поэтому выводы мои были такие:
- SQL и ЯП - разные контексты и первый шаг распила монолита - это поместить их в разные сервисы.
- Если предоставить ЯП некий АПИ по работе с БД, то этот ЯП может быть любым. Плюс заменить БД тоже будет проще.
- Если набрать команду, которая отвечает только за данные и операции с ними, предоставляя АПИ, то.. по мне так это здорово, но это не в тренде и опенсорсных решений для такой команды нет (см. ниже про миграции).
Далее - о том, как я вижу RPC с точки зрения моего опыта в хранимках.
Описание АПИ
АПИ - это набор сервисов, состоящих из методов. Для каждого метода задается сигнатура - имя, аргументы, результат и описание их структур.
Для АПИ результат метода желательно иметь простым (скаляр, структура или массив структур), но в самой БД связи могут быть сложнее (не все поля из N таблиц, а некое, адекватное задаче, представление).
Из этого я сделал вывод, что первичной должна быть схема данных, по которой для каждого метода формируются структуры, которые уже могут быть использованы в описании АПИ.
На этом этапе получается такой план:
- Описать предметную область.
- Спроектировать структуры данных.
- Описать методы и их сигнатуры.
- Загрузить это в БД и проверить корректностью
- По содержимому БД сгенерить описание АПИ (не парсить исходники, а взять метаданные из БД).
- Вот тут распараллелим - БД:реализация хранимок и тестов, Клиенты:реализация.
На текущий момент я под термином “описание АПИ” понимаю protobuf.
Генерация кода
Итак, у нас есть .proto и по нему мы можем сгенерить:
- Интерфейсы для сервиса (например - на golang).
- Клиента (на поддерживаемых генераторами ЯП).
- Спецификацию OpenAPI (со всеми вытекающими).
Но ведь сервис просто будет вызывать хранимки, чего не хватает для его генерации?
- ACL (права доступа).
- Cache (мы работаем с БД, тут это - важно).
Есть варианты:
- Протащить эти два пункта через спецификацию и сразу генерить сервис, который вызывает хранимку на SQL.
- Описать эти метаданные в БД и для генерации сервиса сделать к ней нужные запросы.
На мой взгляд, если в protobuf найдется решение - надо выбрать его, иначе придется сочинить собственный стандарт и там выбрать.
Хранимки
Программный код (функции и процедуры), размещаемый в БД и выполняемый Под этим термином
В чем я вижу их плюсы:
- Вся логика работы с БД инкапсулируется внутри нее, а клиентам доступен только некий АПИ.
- Взаимодействие приложения с БД сводится к запросам вида
select a,b from func(x,y)
. - Даже парсить ничего не надо - уже все есть в метаданных БД, и сигнатуры и комментарии/документация.
Миграции
Насколько мне известно, основной вариант миграций в настоящее время - это оформить в один или несколько файлов все изменения БД текущей миграции. В частности, если надо поменять код хранимки или представления - он дублируется в новом файле с исправлениями (привет, git).
Однако, конструкция CREATE OR REPLACE {FUNCTION|VIEW}
позволяет обновить код в работающей системе. А если обернуть ее в транзакцию и добавить тесты - то обновление сработает только при успехе тестов.
Если мы уберем из рассмотрения системы миграции, которые этот факт не учитывают, что останется?
Мой ответ - написать свою (к слову, мой вариант решения этой задачи в продакшене уже 10 лет).
Тесты
Транзакция, в которой производится миграция, может быть дополнена тестами в виде SQL-запросов, которые при ошибке, сразу или в конце тестов, вызывают EXCEPTION и отменяют все изменения БД.
FAQ
Почему protobuf, а не OpenAPI?
REST - это для HTTP, круг моих задач шире (там нет PUT/DELETE/etc) и мне нужен RPC. А для HTTP я бы предпочел ReadOnly запросы оформить в GET, остальные в POST. И в обоих случаях имя метода хотелось бы видеть в access.log.
Где взять спецов по БД и хранимкам?
Такие люди есть, но рынок их спрятал за “золотой забор”. Все же хотели “SQL+PHP/Ruby/JS/Java/GO/Rust”. В итоге спецы по “вечному” начали размываться и тратить время на “приходящее”. И рынок за это заплатит, я считаю.
С другой стороны, “спецом по БД” принято считать и DBA, но он отвечает за администрирование, для него разработка вторична и это добавляет сложность в позиционирование и поиск персонала.
Т.о., корректный заголовок вакансии мог бы выглядеть так: “Разработчик SQL/PlPgSQL (не DBA)”.
Что-то кроме PostgreSQL?
Сомневаюсь. Я в прошлом веке к нему пришел, с тех пор стараюсь мониторить альтернативы, но ничего лучше не видел. Хотя, на современный Линтер надо бы глянуть.