
Я уже давно думал об одной вроде небольшой, но на самом деле изрядно, по крайней мере меня, доставшей 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 :manySELECT * FROM studiesWHERE 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 # создает рабочий пример модели и операций для sqlcrest gengo run ./cmd/main.go # go test ./...
MongoDB пример:
rest init --example sql # создает рабочий пример модели и операций для sqlcrest gengo 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.
ссылка на оригинал статьи https://habr.com/ru/articles/1056034/