Перейти к основному содержимому

Генератор кода ensi-gog

В Go-сервисах Ensi контракт HTTP API задаётся OpenAPI-спецификацией (как и в PHP-сервисах на Laravel). Чтобы не поддерживать роуты, DTO и заготовки хэндлеров вручную, используется CLI ensi-gog — генератор из репозитория goravel-openapi-generator.

Генератор решает две задачи:

  • ensi-gog server — серверный код внутри сервиса, который описывает API (роуты, типы, хэндлеры, тесты);
  • ensi-gog client — отдельный Go-модуль HTTP-клиента к этому или другому сервису, чтобы потребители вызывали API типизированно.

Бизнес-логику (actions), модели БД, middleware «по смыслу», Kafka и прочую инфраструктуру генератор не пишет — только каркас вокруг контракта API.

Установка​

Версия ensi-gog закрепляется в mise.toml сервиса. Установите инструменты проекта из его корня:

elc mise trust
elc mise run tools:install
elc ensi-gog --version

Файл настроек .ensi-openapi-gen.yaml​

В корне репозитория сервиса, чья спека является источником правды, лежит файл .ensi-openapi-gen.yaml. Все пути в нём считаются относительно этого файла.

Минимально для генерации сервера нужен только путь к entrypoint спеки:

spec: resources/openapi/index.yaml

Для генерации клиента в том же файле добавляют секцию client: (см. ниже).

Перед чтением конфигурации генератор подгружает .env из той же директории, что и YAML. В значениях можно использовать подстановки ${VAR} и ${VAR:-default} — например, каталог вывода клиента через ${OPENAPI_CLIENT_OUTPUT_DIR}.

Если во внешней или общей спеке встречаются одинаковые имена схем в разных файлах, их можно развести через component_aliases (ключ — путь фрагмента относительно корня OpenAPI, значение — уникальное имя типа). Это работает и для server, и для client.

Где лежит OpenAPI в Go-сервисе​

Точка входа — resources/openapi/index.yaml. Модули API обычно разнесены по подпапкам (offers/, auth/, categories/), рядом лежат *_paths.yaml и schemas/. При генерации серверного кода, эти папки становятся модулями приложения. Примеры:

  • auth/paths.yaml → код в app/modules/auth/...;
  • cms/banners/paths.yaml → app/modules/cms/banners/... (вложенные каталоги сохраняются);

Общие параметры и ошибки определяются в корневых файлах вроде common_parameters.yaml.

Имя HTTP-контроллера и метод хэндлера задаются в спеке расширением x-lg-handler на операции:

OffersSearch:
post:
operationId: searchOffers
x-lg-handler:
handler: OffersController
method: search

Для обратной совместимости со спеками в сервисах на php, x-lg-handler может принимать строку, например App\Http\ApiV1\Modules\Seo\Controllers\SeoTemplateProductsController@get

Из этого генератор строит структуру OffersController, метод Search и файл тестов search_test.go. Дополнительные middleware на группу роутов можно описывать через x-lg-middleware (как принято в вашей спеке Ensi).

Подробнее про общие правила дизайна API — API Design Guide.

Команда ensi-gog server​

Запуск из корня сервиса (где лежит .ensi-openapi-gen.yaml):

elc ensi-gog server

Или с явным путём к настройкам:

elc ensi-gog server ./.ensi-openapi-gen.yaml

Генератор собирает (bundle) спецификацию во временный файл, разбирает операции и создаёт или дополняет артефакты:

Что появляетсяКудаЗачем
Роутыroutes/api_gen.goРегистрация /api/v1/... и привязка к хэндлерам
Action routerapp/common/http/actionrouter/actionrouter.goДиспетчер операций /{id}:action, обновляется генератором при наличии таких маршрутов
Хэндлерыapp/modules/{module}/handlers/Точка входа HTTP; stub отвечает 501, пока не реализуете
DTOapp/modules/{module}/dto/Тела запросов и типы data в ответах
Enum'ыapp/enums/*.gen.goПеречисления из спеки
Фабрики запросовapp/modules/{module}/factories/Заготовки для тел POST/PATCH в тестах
Component-тестыapp/modules/{module}/tests/{handler}/Заготовки TestStatus_* по кодам ответа из спеки
Хелперыapp/common/http/validate.go, map.goВалидация FormRequest и маппинг в DTO (создаются один раз)

Файл routes/api.go создаётся один раз и вызывает сгенерированный RegisterApiRoutes(). Его можно расширять, но вызов регистрации API лучше оставить.

Для request-DTO с телом запроса генератор добавляет методы Authorize и Rules (как FormRequest в Goravel). Для response-data — метод From(src any) через общий маппер. Обёртки вроде «ответ с полем errors» в Go-код не генерируются: в типах остаётся только полезная нагрузка (data), а формат ответа задаётся общими хелперами HTTP-слоя.

Заготовка метода хэндлера обычно валидирует request (если есть схема) и возвращает 501 Not Implemented. Реализацию (вызов action, маппинг, ошибки) пишете вы.

Что можно править после генерации​

При обычном запуске без --force генератор сохраняет доработки в предусмотренных точках расширения:

  • Тела методов хэндлеров — свободно меняете; новые action из спеки добавляются в тот же файл, существующие методы с тем же именем не перезаписываются.
  • Методы на DTO — Authorize, Rules, From и любые свои; генератор обновляет только объявления полей struct из спеки.
  • Свои типы в том же dto/*.go, которых нет в OpenAPI — остаются.
  • Фабрики запросов — существующие файлы сохраняются; можно менять Definition() и добавлять свои методы With*. После изменения request-схемы проверьте соответствие фабрики новым полям.
  • Component-тесты — после первого создания файла генератор их не трогает; дополняете сценарии сами (см. автотесты на Go).
  • validate.go / map.go — не перезаписываются даже с --force.

Флаг --force может перезаписать реализации handler-ов, фабрики запросов и компонентные тесты. Перед его применением сохраните изменения в Git и отдельно проверьте diff. Обычная рабочая генерация и check:generated выполняются без этого флага.

Что нельзя править вручную (или правки пропадут)​

  • routes/api_gen.go — файл с пометкой DO NOT EDIT; пути, методы и middleware задаются в OpenAPI. Изменения механизма генерации выполняют в конфигурации или самом генераторе с последующей перегенерацией.
  • app/common/http/actionrouter/actionrouter.go — общий runtime для action routes, обновляется генератором; исправление вносят в генератор.
  • Поля сгенерированных struct DTO и их json/form теги — при следующей генерации type-блок подменится из спеки. Новые поля добавляйте в YAML-схемы.
  • Файлы *.gen.go с enum'ами — перед генерацией пересоздаются; значения только в спеке (x-lg-enum-class и т.д.).
  • Имена модулей, контроллеров, action — задаются путём в OpenAPI и x-lg-handler; переименование только в Go без спеки приведёт к дублям.

Важный нюанс: если вы уже написали Rules() вручную, а в спеке изменился набор полей request, генератор обновит struct, но не перепишет ваш Rules(). После изменения схемы правила валидации нужно проверить и поправить самостоятельно.

Генератор не создаёт actions, модели БД, policies и query-контракты для search — это остаётся за разработчиком (см. структуру сервиса).

Action routes /{id}:action​

Laravel-совместимые URI вроде /banners/{id}:upload-file сохраняются в OpenAPI. Генератор регистрирует базовый маршрут для HTTP-метода, а общий actionrouter выбирает handler по суффиксу:

actionrouter.Register(api, "Post", "/banners/{id}", "id", []actionrouter.Operation{
{Suffix: "upload-file", Handler: bannersController.UploadFile},
})

Это пример сгенерированного объявления. Если для того же метода есть обычная операция /{id}, она включается в таблицу с пустым суффиксом. У каждой операции сохраняются собственные controller и порядок middleware. Handler получает очищенный id через route accessor.

Неизвестный action или пустой ID возвращает 404 и не должен попадать в обычную или удаляющую операцию. Runtime использует Gin; при замене HTTP-драйвера потребуется проверить поддержку этого механизма. Старый метод Action в controller может сохраниться, но актуальные сгенерированные маршруты вызывают методы операций напрямую.

Проверяйте оригинальные URI с двоеточием: успешный вызов, ошибки, конфликт с обычным /{id} и неизвестный суффикс без побочных эффектов. Замена URI на /{id}/action меняет публичный контракт.

Проверка воспроизводимости​

После генерации выполните:

elc mise run check:openapi
elc mise run check:generated

check:openapi проверяет спецификацию, а check:generated запускает генератор и проверяет, что состояние файлов не изменилось. Если проверка находит diff, сначала воспроизведите генерацию и разберите причину. Ручная правка generated-файлов или постобработка маршрутов после генератора скрывает расхождение с контрактом.

Эти проверки не доказывают совместимость с Laravel и корректность бизнес-логики: нужны сравнение исходного контракта и компонентные тесты.

Команда ensi-gog client​

Секция client в .ensi-openapi-gen.yaml описывает, куда положить сгенерированный модуль-клиент и как он будет называться в экосистеме Ensi.

Пример:

spec: resources/openapi/index.yaml

client:
name: catalog-cache
output: ${OPENAPI_CLIENT_OUTPUT_DIR}-go
module: gitlab.com/greensight/ensi/catalog/clients/catalog-cache-client-go
repository: gitlab.com/greensight/ensi/catalog/clients/catalog-cache-client-go
base_url_env: CATALOG_CATALOG_CACHE_GO_SERVICE_HOST
http_runtime: gitlab.com/greensight/ensi/packages/goravel-ensi-http-client
go_mod: |
go 1.25.0

require (
gitlab.com/greensight/ensi/packages/goravel-ensi-http-client v0.0.0-20260806150005-69f7fd80d898
github.com/goravel/framework v1.18.0
github.com/oapi-codegen/runtime v1.6.0
)

Кратко по полям:

  • name — короткий идентификатор (влияет на название файла провайдера и ключ конфига ensi_clients.{snake_name});
  • output — каталог модуля клиента (часто через переменную из .env);
  • module — путь go.mod сгенерированного клиента;
  • repository — Git-репозиторий клиента (для документации);
  • base_url_env — имя переменной окружения с базовым URL сервиса чтобы генератор прописал его в конфиг клиента;
  • http_runtime — имя пакета goravel-ensi-http-client, чтобы генератор прописал его во все импорты клиента;
  • go_mod — тело go.mod клиента без строки module (версии зависимостей).

Запуск из окружения сервиса-источника. Файл настроек и output-каталог должны быть доступны в контейнере ELC:

elc mise exec -- ensi-gog client ./.ensi-openapi-gen.yaml

Клиент генерируется целиком из OpenAPI: типы, методы API, фабрики для тестов, fake-обёртки для component-тестов потребителей. После изменения спеки перегенерируйте клиент и обновите зависимость в сервисах-потребителях.

Подключение клиента в Goravel-сервисе​

Сгенерированный клиент оформлен как Goravel package. Сначала добавьте выбранную версию модуля через elc go get, затем установите пакет из корня потребителя:

elc ./artisan package:install gitlab.com/greensight/ensi/cms/clients/cms-client-go

После установки в потребителе появляются:

  • app/providers/clients/{name}_provider.go — регистрация клиента в DI (можно добавить middleware логирования/метрик);
  • config/ensi_clients/{name}.go — чтение URL из env (base_url_env из настроек генерации).

Installer также добавляет facade и регистрации. Просмотрите изменения в bootstrap, config/app.go, app/providers и config/ensi_clients, сохраните существующую сборку приложения и настройте middleware. Опубликованные provider и config можно редактировать под проект. При обновлении библиотеки проверьте их отдельно от зависимости в go.mod.

В тестах потребителя stub ответов апстрима делается через Fake() (возвращает *ClientFake) и Bind() — см. автотесты.

Типичный рабочий процесс​

  1. Изменили контракт в resources/openapi/ (paths, schemas, x-lg-handler).
  2. Запустили elc ensi-gog server, проверили diff и задачи check:openapi / check:generated.
  3. Реализовали или поправили action и тело хэндлера; при необходимости обновили Rules().
  4. Дописали component-тесты поверх заготовок TestStatus_*.
  5. Для потребителей API запустили elc ensi-gog client, опубликовали изменения клиента и обновили зависимости потребителей.

Спека остаётся единым источником правды: документация (/docs/oas), серверный код и клиенты расходятся из одного index.yaml.