Генерируем CRUD для gRPC по схеме БД следуя Google AIP
Как с помощью db-exporter автоматизировать процесс генерации CRUD-операций для gRPC, соблюдая стандарт Google AIP
255 открытий3К показов
К сожалению, жизнь разработчика не всегда полна сложными и интересными задачами, время от времени мы все-таки пишем CRUD'ы.
Задача в целом проста:
- Открыть структуру таблицы
- Скопировать названия колонок
- Соотнести типы, которые существуют в спецификации (gRPC, OpenAPI, и т.п.)
- Описать API метод
- Проверить на соответствие стайл-гайду
Но это утомительно и отнимает драгоценное время разработчиков. Данную инструкцию за нас может выполнить инструмент db-exporter. В этой статье рассмотрим, как с его помощью автоматизировать процесс генерации CRUD-операций для gRPC, соблюдая стандарт Google AIP по ресурсно-ориентированному дизайну.
В последующих разделах мы подробно рассмотрим:
- Принцип работы db-exporter
- Процесс настройки и конфигурации
- Примеры генерации различных типов операций
- Валидацию сгенерированных proto файлов
- Как внедрить инструмент для постоянной работы с ним
Знакомимся с инструментом
db-exporter — это Open Source утилита для генерации кода и документации к схеме базы данных. «Экспортировать» можно в различные распространенные форматы: диаграмма классов, protobuf, код на Go и другие. На данный момент инструмент поддерживает PostgreSQL и MySQL. Реализована поддержка основных операционных систем. Инструмент достаточно молодой и активно развивается.
Для начала работы с db-exporter необходимо скачать его со страницы релизов. Далее разархивировать и переместить в /usr/local/bin. Или выполнить всё одной командой.
Mac OS
Linux
Теперь у нас установлен инструмент и мы почти готовы его запускать, но перед этим разберемся, составим пример таблицы, над которой будем проводить эксперименты.
Составим пример
Договоримся, что нашим примером для генерации кода будет являться таблица пользователей. У пользователей есть: id, имя, отчество, дата создания, статус (активен, заблокирован) и дата удаления.
Итого, схема выглядит так:
Данного примера достаточно для того, чтобы рассмотреть ключевые моменты:
- Генерация первичных ключей (поле id)
- Генерация опциональных полей (поле middle_name)
- Генерация временных меток (поля created_at, deleted_at)
- Генерация енамов (поле status, енам user_status)
- Работа с soft-delete
Генерируем protobuf
Для того, чтобы запустить db-exporter необходимо описать конфигурацию —делается это декларативно, в yaml-файле. db-exporter оперируют задачами, в которых описаны:
- Формат, в котором нужно выполнить генерацию. В нашем случае — это
grpc-crud
- Директория, в которую сохранить файлы
- Специфичные формату параметры (далее: спека задачи)
Также важно не забыть указать подключение к базе данных. В нашем примере, используется PostgreSQL на 5432 порту. Для успешного запуска стоит убедиться, что БД запущена и в ней есть схема с таблицами.
Итого, конфигурация, описывающая перечисленные требования, выглядит следующим образом:
Сохраняем этот файл, как .db-exporter.yaml.
Далее запускаем инструмент, выполняя команду db-exporter и получаем отчет о проделанной работе — db-exporter сообщает о том, какие файлы были созданы и какой они имеют вес.
Разбираемся, что получили на выходе
В результате выполнения команды был сгенерирован файл users.proto, в котором используется синтаксис соответствует proto3. Файл содержит:
- UsersService с методами List, Get, Delete, Undelete, Create, Patch
- Сообщение User, отражающее таблицу users
- Енам UserStatus
Сообщение User и enum UserStatus
Сообщение User отражает структуру таблицы users и имеет вид:
Что здесь видим?
id- uuid'ы генерируются, как строки. Первичный ключ помечается, какOUTPUT_ONLY, говоря о том, что поле используется только для ответов.name,emailсгенерированы, как строки- Поля
created_at,deleted_atобернуто в гугловый Timestamp - Для статуса пользователя сгенерирован енам UserStatus
UserStatus выглядит следующим образом
Метод для получения списка пользователя
Метод List принимает на вход ListUsersRequest и возвращает ListUsersResponse
Запрос включает в себя поле с массивом идентификаторов пользователей. db-exporter добавляет в тело запроса первичный ключ, если первичный ключ состоит из одного поля. Также в запросе присутствует флаг show_deleted, как того требует стандарт AIP-132, при работе с сущностями, поддерживающими soft-delete: по умолчанию сервер возвращает список активных пользователей, например, используя sql-запрос с фильтром deleted_at is null.
По умолчанию db-exporter добавляет token-based пагинацию, описанную в AIP-158. Такая пагинация удобна для межсервисного взаимодействия. Но если основной клиент вашего API — это фронтенд, то вероятнее всего, вам нужна офсетная пагинация. Ее можно включить, добавив в спеку задачи pagination: offset. Если пагинация уж совсем не требуется, то pagination: none.
В ответе же список пользователей и токен следующей страницы.
Метод получения пользователя
Стандарт по получению ресурса описывает достаточную простую конструкцию — Метод Get принимает на вход GetUserRequest и отдает на выход сообщение User.
Тело запроса включает в себя поле id, так как оно является первичным ключом.
Методы удаления и восстановления пользователя
Стандарт AIP-164 гласит: мало того, что сущность удаляется методом Delete, так еще нужно и мочь ее восстанавливать с помощью метода Undelete, если таблица поддерживает soft-delete.
Поэтому в результате генерации мы получаем методы Delete и Undelete: db-exporter самостоятельно определяет поддержку soft-delete у таблиц, смотря на поля deleted_at и delete_time.
Метод Delete принимает на вход DeleteUserRequest и отдает на выход DeleteUserResponse. Метод Undelete принимает UndeleteUserRequest и возвращает восстановленный ресурс User.
Тела запросов также, как и для метода Get, содержат поле первичного ключа — id.
По умолчанию возвращается google.protobuf.Empty, как требуется в стандарте. Возможна генерация пустого сообщения DeleteUserResponse — для этого нужно добавить параметр в спеку:
Методы создания и обновления пользователя
По стандартам AIP-133 и AIP-134 методы Create и Update имеют похожие сигнатуры.
Тела запросов также схожи, включают в себя ресурс User и возвращают его обновленную версию.
По стандарту поле update_mask предполагается использовать для частичного обновления сущности. FieldMask содержит в себе список полей, которые необходимо обновить у сущности User.
Добавляем HTTP пути к методам
В gRPC есть полезная опция google.api.http, с помощью которой можно указать HTTP адрес для метода. Ее полезно использовать, если вы используете REST поверх gRPC сервисов — например, с помощью grpc-gateway.
Наверняка, мы хорошие разработчики и версионируем свой API: установим версию v1 в параметре path_prefix.
И получаем вот такой результат:
Валидируем результат
Теперь, когда у нас есть файл users.proto — важно проверить, что он корректен синтаксически. Для этого соберем go-клиент, используя утилиту protoc.
Указываем Go пакет
Выше мы обсуждали gRPC контракты без привязки к конкретному стеку. Ну что ж, время пришло!
Для генерации Go-клиента в .proto файле необходимо указать опцию go_package. Мы можем сами поправить .proto файл, но при частом использовании db-exporter это будет не так удобно, поэтому скажем экспортеру, какой пакет нам нужен. Для этого нужно указать опцию в спеке задачи .db-exporter.yaml:
Нам остается еще раз сгенерировать users.proto, используя уже знакомую команду db-exporter.
Собираем зависимости
Сгенерированный users.proto импортирует зависимости google.api для указания HTTP методов/путей, обязательности полей. Их можно скачать с GitHub googleapis: http.proto, field_behavior.proto и annotations.proto.
Скачать зависимости можем следующим скриптом:
Итого мы должны были организовать следующую структуру файлов:
Теперь запустим генерацию гошного кода:
В результате выполнения команды в наш проект добавились 2 новых файла:
pkg/api/users.pb.go— структуры для ресурса, запросов и ответовpkg/api/users_grpc.pb.go— rpc-сервис UserService и клиент к нему
Вывод: из сгенерированного db-экспортером .proto файла можно сгенерировать валидный програмнный код. Считаем проверку успешно выполненной
Непрерывная генерация
Перезапись файлов
Собранная нами конфигурация отлично подходит для «одноразовых генераций», но со временем, когда сервис обрастет новыми методами, использовать генератор будет неудобно из-за того, что он по умолчанию перезаписывает уже существующие файлы. Чтобы избежать такой ситуации, необходимо указать генератору, что мы не хотим перезаписывать файлы. Сделать это можно, добавив опцию skip_exists.
Теперь db-exporter не будет перезаписывать уже существующие файлы и будет генерировать только новые.
Генерация лишних таблиц
По умолчанию db-exporter генерирует файлы по всем таблицам из схемы БД. В проекте может десяток или десятки таблиц, не для всех из них нужна генерация CRUD-операций. Генератору можно передать список таблиц, по которым необходимо генерировать файлы.
Но править постоянно конфиг не очень удобно. Укажем переменную.
Теперь мы можем запускать генерацию по конкретным таблицам, например вот так:
Или обернуть это в Makefile:
Использовать эту конструкцию можно следующим образом:
Что в итоге?
В итоге мы собрали рабочую конфигурацию db-exporter, которая позволяет генерировать CRUD по схеме БД в валидный .proto файл, включая полезные опции: google.api.field_behavior и google.api.http. Собранную конфигурацию и необходимое окружение вы сможете найти по этой ссылке.
Надеюсь, этот пост был для вас полезным и поможет ускорить разработку крудов. Если у вас есть предложения по улучшению/расширению кодо-генератора, добро пожаловать в репозиторий.
В репозитории вы также сможете найти примеры генерации диаграмм классов, mermaid, markdown и так далее.
255 открытий3К показов





