← Назад к новостям

Query‑first подход или как из SQL запросов или MongoDB контрактов получить готовое REST API

Я уже давно думал об одной вроде небольшой, но на самом деле изрядно, по крайней мере меня, доставшей backend‑проблеме. В зависимости от подхода, конечно, но зачастую, мы уже знаем, какие операции и с какими сущностями нам предстоит работать, создавая REST сервис. Но написание моделей и запросов к базе данных всегда проходит лишь как один из этапов написания слоистой архитектуры будущего приложения/микросервиса. Несмотря на опыт, всё же приходится многократно проходить ручную сборку одних и тех же слоёв по каждому домену, не говоря уже о нейминге эндпоинтов, хендлеров, хелперов и так далее... Почти для каждой новой доменной модели мы проходим один и тот же путь: — написать методы репозитория; — добавить сервисный слой; — создать HTTP handlers; — описать request и response модели; — реализовать auth и авторизацию с ролями для эндпоинтов; — написать/обновить OpenAPI spec; — добавить curl‑примеры; // по желанию, но мне часто нравилось работать с ними — написать тесты; — подключить логирование, Docker, health checks, auth и кучку middleware. Большая часть работы повторяется из проекта в проект. Беря за основу любимый шаблон все равно мы долго топчемся над реализацией однотипных сущностей прыгая по этому snakeCase, фантазируя над наименованием очередного сервиса, интерфейса, хендлера, хелпера... кажется я уже повторяюсь:). Подходов к началу создания REST сервиса, как мы знаем, немного: — spec‑first — code‑first — whateverIusedToDo‑first — как привык, так пишу + зависит от настроения и погоды // самый частый не признанный, но всеми любимый — query‑first — мне всегда импонировал, но дать название этому подходу (по крайней мере в литературе не встречал сам — ни на что не претендую и готов признать, коли укажете, что такое есть) вдруг захотелось в этом году...а потом долго писал первые пару строк генератора, да-да, люблю писать сам...но только недолго:). Что я называю query‑first Идея простая: если есть модель и операция с базой данных уже описывает, что конкретно приложение должно делать с тем или иным json, можно использовать эту операцию для создания абсолютно (почти) всего REST приложения... Не претендую быть первым кто это придумал, а может и реализовал, ну разве что 1001ым:). Для SQL проектов это выглядит так: PostgreSQL schema + SQLC queries ↓ Generated endpoint inventory ↓ Repository → Service → HTTP handlers ↓ http server со всеми "примочками" + logging + OpenAPI + auth policies + tests + observability + docker + docker compose + ci/cd Для MongoDB проектов идея похожая, только вместо SQLC используются явные YAML‑контракты: MongoDB collection contracts ↓ Generated endpoint inventory ↓ Repository → Service → HTTP handlers ↓ http server со всеми "примочками" + logging + OpenAPI + auth policies + tests + observability + docker + docker compose + ci/cd endpoint inventory — это внутренняя модель, которая знает: — HTTP method и path; — исходный SQL query или Mongo method; — path/query/body параметры; — auth policy; — роли; — системные routes вроде health, readiness, Swagger и metrics. Генератор может создавать разные части приложения из одного источника правды. Handler, OpenAPI operation, curl‑пример, тест и auth policy не должны каждый отдельно угадывать, что такое endpoint. Они должны строиться из одного inventory. В общем, реализовал я эту идею в open‑source Go CLI под названием rest . Не могу не сказать, что вдохновил меня изначально давний зарекомендовавший себя проект sqlc . Именно этот проект вдохновил меня сначала начать, затем продолжить, начатое давно и наконец завершить первый релиз. SQLC превращает SQL в type‑safe Go код. rest делает следующий шаг: берёт SQLC output или MongoDB contracts и превращает их в запускаемое слоистое REST приложение. Цель не в том, чтобы сгенерировать финальный продукт. Цель простая — автоматизировать — убрать скучную и склонную к ошибкам рутину, чтобы разработчик стартовал не с пустой папки, а с хорошего каркаса приложения и дальше писал настоящую бизнес‑логику. В простых проектах это может дать почти готовое приложение. В более сложных — всё равно снять первые 80–90% повторяющейся работы. SQL пример Представим таблицу: CREATE TABLE studies ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), patient TEXT NOT NULL, study_type TEXT NOT NULL, time_beginning TIMESTAMP, surgeon TEXT NOT NULL, deleted BOOLEAN NOT NULL DEFAULT FALSE ); И sqlc query: -- name: GetStudies :many SELECT * FROM studies WHERE deleted = false AND ( sqlc.narg('type')::text IS NULL OR study_type = sqlc.narg('type') ) AND ( sqlc.narg('surgeon')::text IS NULL OR surgeon = sqlc.narg('surgeon') ) ORDER BY time_beginning DESC; Далее должно идти выполнение команды sqlc generate самостоятельно или автоматическое в рамках самой команды rest gen при активной опции auto_sqlc: enable в файле настроек rest.yaml SQLC создаёт type‑safe Go код для этого запроса. rest может на его основе вывести endpoint вроде: GET /studies?type=CT&surgeon=Ivanov Опциональные sqlc.narg(...) значения становятся optional query parameters. Result type становится частью response model и OpenAPI schema. MongoDB пример Для MongoDB нет прямого аналога SQLC, поэтому я использовал явные YAML‑контракты. Mongo contracts лежат здесь: rest_config/rest_mongo/*.yaml. Каждый entity file может описывать: — collection name; — fields; — indexes; — CRUD methods; — custom methods вроде find_one , find_many , update_one , delete_one и aggregate ; — HTTP path/method mapping. Сгенерированное Mongo app включает repositories, services, handlers, OpenAPI, handler tests, auth middleware, Docker support и runtime health/readiness paths. Mongo domain layer сделан достаточно гибким для document data. Что генерируется В зависимости от конфигурации rest.yaml команда rest gen может сгенерировать: — cmd/main.go ; — domain models; — PostgreSQL или MongoDB repositories; — service layer; — HTTP handlers; — request/response models; — handler tests; — OpenAPI и Swagger UI; — JWT или Basic Auth; — RBAC route wrapping; — security headers; — rate limiting; — production‑safer CORS defaults; — request IDs; — panic recovery; — body size limits; — Zap logging; — optional Prometheus metrics; — Dockerfile; — optional Docker Compose; — .env.example ; — Makefile; — init_db shell script для локального postgres — CI/CD workflow templates; — curl examples. — DEPLOYMENT.md next steps guide — ARCHITECTURE.md c текущей схемой архитектуры проекта — README шаблон с инструкцией по запуску проекта Сгенерированный Go код форматируется линтерами автоматически, поэтому imports и общий стиль кода сразу приводятся к нормальному виду. Auth generation Auth оказался одной из самых сложных частей дизайна. Поддерживается пока 2 варианта JWT и BasicAuth. Flow такой: 1. Указать auth: enable в rest.yaml . 2. Запустить rest gen . 3. Генератор найдёт endpoints и создаст rest_config/auth_rest.yaml . 4. Разработчик выбирает вариант auth и отмечает, какие endpoints публичные, какие защищены, а какие требуют роли. 5. Снова запустить rest gen . 6. Генератор обновит middleware, route wrapping, auth handlers и OpenAPI security. Для SQL/JWT приложений генератор может создать signup/signin handlers, password hashing, token service, claims configuration и protected routes на основе настроенной user model. Для Basic Auth и Mongo apps генерируется соответствующий middleware и role checks. rest doctor Да, и такая есть фича. Её можно запускать на любом этапе: — после rest init ; — после редактирования YAML файлов; — после rest gen ; — перед попыткой запустить сгенерированное приложение. Она проверяет согласованность конфигов, наличие нужных tools, YAML‑ошибки, готовность Docker, OpenAPI/auth configuration, generated files и наличие необходимых библиотек. Это как раз та вещь, которой мне не хватало много лет назад, в самом начале пути: команда, которая говорит, чего не хватает и что делать дальше... в точности, как сейчас любой ai powered cli. Что проект не пытается решить rest не знает вашу бизнес‑логику. Он не будет генерировать: — сложную domain validation; — custom workflows; — billing logic; — external integrations; — сложные transactions; — product‑specific authorization rules. — неудобный реальный SQL query с которым не справится sqlc generate или «крайне сложный» Mongo contract Генератор нужен, чтобы создать сильную и согласованную стартовую точку. После этого разработчик продолжает обычную разработку. К генератору может возвращаться только тогда, когда появляются новые domain models или queries, для которых снова нужно собрать все слои и добавить к существующим — об этом позаботится скромная опция safe_reload: enable из rest.yaml, которая спросит вас, сохранить ли ваш кастомный код при повторной генерации. Как попробовать Install: go install github.com/repomz/rest/cmd/rest@latest SQL пример: rest init --example sql # создает рабочий пример модели и операций для sqlc rest gen go run ./cmd/main.go # go test ./... MongoDB пример: rest init --example sql # создает рабочий пример модели и операций для sqlc rest gen go run ./cmd/main.go # go test ./... Реальный workflow: rest init # Создаёт rest_config/ с базовыми настройками генератора. # Перед генерацией нужно заполнить rest.yaml и добавить контракты: # SQLC schema/queries для PostgreSQL или YAML-контракты для MongoDB. rest gen go run ./cmd/main.go # go test ./... После генерации проекта можно взглянуть на схему ендпоинтов: rest list endpoints # всегда покажет все актуальные ендпоинты с их текущими опциями или просто заглянуть в swagger ui: http://localhost:8080/swagger В README.md вас будет ждать пара шильдиков и текущая архитектура вашего приложения: my-app/ ├── cmd/ │ └── main.go # entrypoint приложения ├── internal/ │ ├── config/ # загрузка env/config │ ├── db/ # SQLC models/queries или Mongo client │ ├── repository/ # доступ к данным │ ├── service/ # бизнес-слой │ ├── transport/ │ │ └── http/ # handlers, routes, middleware │ ├── auth/ # JWT/Basic Auth, RBAC │ ├── logger/ # Zap logging │ └── observability/ # health, readiness, metrics ├── api/ │ └── openapi.yaml # Swagger/OpenAPI схема ├── examples/ │ └── curl/ # curl-примеры запросов ├── rest_config/ │ ├── rest.yaml # основные настройки генерации │ ├── auth_rest.yaml # политики доступа к endpoints │ ├── rest_sqlc/ # SQLC schema/queries/config │ │ ├── rest_sqlc.yaml │ │ ├── schema.sql │ │ └── queries.sql │ └── rest_mongo/ # Mongo contracts, если используется MongoDB │ ├── rest_cheatsheet.yaml │ └── user.yaml ├── Dockerfile ├── docker-compose.yaml ├── Makefile ├── .env.example ├── go.mod └── go.sum Почему и зачем Я не утверждаю, что query‑first — единственный правильный способ строить API, но я думаю это просто УДОБНО. Spec‑first отлично подходит, когда OpenAPI является центральным контрактом. Свой Code‑first удобен для многих команд и у каждого beckend'ера конечно возможно есть свой личный кастомный шаблон/генератор. Ручной код остаётся правильным ответом, когда главная сложность — в бизнес‑логике. Но свой whateverIusedToDo‑first я точно апгрейдил и с удовольствие делюсь с вами, не предтендуя ни на что ни в коем разе, а наоборот, имея большое желание получить нескромный совет по доработке, улучшению и определенно по борьбе с bug issues. Ну, а кому проект возможно сэкономит хоть сколько то времени в его работе и станет, возможно, очередным полезным инструментом, могу с улыбкой только пожелать — Give yourself a little rest . Если идея кажется интересной, WELCOME TO COLLABORATE and rest together. Write queries. Get an application. Add business logic.
📊 Источник: Habr | Оригинал