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

Структура backend сервиса на языке Go

Сервисы Ensi на Go строятся на Goravel и сохраняют те же организационные цели, что и PHP-сервисы:

  1. группировка кода по предметной области (модулю), а не по техническому слою на весь сервис;
  2. классы/типы меньше и сильнее следуют SRP.

Новые сервисы создаются из backend-service-skeleton-go. Примеры прикладного кода есть в CMS-Go, Catalog Cache Go и OMS Go.

Ниже описаны общие границы слоёв. Состав инфраструктуры определяется задачами сервиса: например, OMS использует Kafka и внешние cron-команды, а Catalog Cache также использует Elasticsearch, очереди и scheduler. Наличие файлов в шаблоне само по себе не означает, что подсистему нужно включать.

Пошаговая настройка окружения, создание сервиса и первый тест описаны в первом запуске.

Практические руководства​

ЗадачаРуководство
Соблюсти принятые правила Go-кодаСтиль и правила
Реализовать SQL :searchФильтры, includes и пагинация
Создать, изменить или удалить ресурсИзменяющие операции
Вызвать API другого сервисаHTTP-клиенты
Загрузить, заменить или удалить файлФайловые операции

Окружение и инструменты​

Команды Go, Artisan и инструментов проекта выполняют из корня сервиса через elc; для тестов передают --env=.env.testing.

elc mise trust
elc mise run tools:install
elc go build -o build/app
elc go test ./... --env=.env.testing

Подробности версий, установки клиентов и локального workspace — в работе с пакетами.

Общий обзор​

app/
adapters/ # инфраструктура: БД-модели, Kafka, очередь, логи, метрики
common/ # общие HTTP-хелперы, search, files, initial-event, metrics
enums/ # enum'ы, сгенерированные из OpenAPI
errors/ # типизированные ошибки приложения
facades/ # facades Goravel
modules/ # бизнес-модули (аналог Domain + Http Modules в PHP)
providers/ # регистрация провайдеров и HTTP-клиентов
bootstrap/ # сборка приложения, провайдеры, команды, jobs, kafka
config/ # конфигурация приложения и зависимостей
database/ # миграции и сидеры
resources/openapi/ # OAS3 спецификация API
routes/ # web, API; gRPC подключается только при необходимости
tests/ # общие инструменты компонентного/OpenAPI тестирования

app/modules​

Бизнес-логика и HTTP-слой модуля живут рядом в app/modules/{module}. Название модуля обычно во множественном числе и соответствует основной сущности: offers, products, brands.

Исключения:

  • common — сквозная функциональность сервиса (миграции сущностей, failed jobs, служебные команды);
  • инфраструктурные модули вроде elastic / elastic_offers, если работа с индексом — отдельная предметная область;
  • examples — демо-код для Kafka/queue/commands, не продакшен-логика.

app/modules/{module}​

Типичный набор пакетов внутри модуля:

ПакетНазначение
handlersHTTP-контроллеры (точка входа API)
actionsбизнес-действия с методом Execute
queriesконтракт search/filter/sort/include для списков
mappersпреобразование adapters/db моделей в API DTO
dtorequest/response структуры
testsкомпонентные тесты эндпоинтов модуля
commandsartisan-команды, относящиеся к модулю
jobsфоновые задачи очереди
kafkaобработчики сообщений Kafka
factoriesфабрики запросов для тестов

handlers​

Handler принимает HTTP-запрос, валидирует его, вызывает action и формирует ответ. Он не содержит бизнес-логики и не ходит в БД напрямую. Ответ и ошибки собирают хелперами из app/common/http — см. HTTP-ответы и ошибки.

func (c *OffersController) Search(ctx http.Context) http.Response {
var req commondto.SearchRequest
if failResponse := commonhttp.Validate(ctx, &req); failResponse != nil {
return failResponse
}

result, err := actions.NewSearchOffersAction().Execute(ctx.Context(), req.Params())
if err != nil {
return commonhttp.Error(ctx, "ServerError", err)
}

return commonhttp.Page(ctx, result.Data, result.Pagination)
}

actions​

Action — аналог Action-классов в PHP: осмысленное самостоятельное действие с публичным методом Execute. Action не должен знать про HTTP/Kafka-транспорт: на вход получает context.Context и типизированные параметры/DTO, на выход — результат или ошибку.

func (a *SearchOffersAction) Execute(ctx context.Context, params search.Params) (*commondto.SearchResult[dto.Offer], error) {
query := facades.Orm().WithContext(ctx).Query().Model(&db.Offer{})
data, pagination, err := search.Execute(query, queries.Offers(), params, mappers.OfferFromModel)
if err != nil {
return nil, err
}
return &commondto.SearchResult[dto.Offer]{Data: data, Pagination: pagination}, nil
}

queries​

Для search-эндпоинтов модуль описывает публичный SQL-контракт: доступные includes, sorts и filters. Общий движок лежит в app/common/search.

mappers​

Mappers отделяют ORM-модели (app/adapters/db) от API DTO. Это позволяет менять структуру хранения, не ломая контракт ответа, и учитывать include при сборке ответа.

dto​

DTO — структуры запроса/ответа API. Общие search-структуры лежат в app/common/dto.

app/adapters​

Инфраструктурный слой, общий для всего сервиса:

  • adapters/db — ORM-модели и фабрики моделей (аналог Eloquent Models из PHP Domain). Модели не раскладываются по модулям: одна таблица — один файл в adapters/db. Подробнее: работа с базой данных;
  • adapters/kafka — клиент, registry, supervisor consumers/producers. Как пользоваться: работа с Kafka;
  • adapters/queue, adapters/schedule — обвязка воркеров очереди и планировщика. Про очереди: работа с очередями;
  • adapters/logging, adapters/prometheus, adapters/observability — логирование и метрики. Про логи: логирование; про Prometheus: метрики;

Бизнес-модули зависят от adapters, но adapters не должны зависеть от конкретного HTTP-слоя модулей.

app/common​

Общие переиспользуемые компоненты без привязки к конкретному домену:

  • http — валидация, единообразные ответы и ошибки, middleware;
  • search — выполнение search-запросов по search.Definition;
  • dto, вспомогательные функции коллекций, metrics;
  • files — преобразования файловых метаданных;
  • initialevent — контекст инициирующего события.

Типизированные ошибки приложения живут отдельно в app/errors; HTTP-хелперы преобразуют их в публичные ответы.

Служебные HTTP-ручки вроде healthcheck/OpenAPI UI лежат в app/common/handlers и подключаются из routes/web.go.

routes​

Роуты живут в корневой директории routes/:

  • api.go — точка входа API, вызывает сгенерированную регистрацию;
  • api_gen.go — сгенерированные API-роуты (DO NOT EDIT);
  • web.go — служебные маршруты (health, OpenAPI UI);

Сгенерированные роуты создают контроллеры из app/modules/{module}/handlers и вешают их на /api/v1/....

resources/openapi​

Описание API в OAS3 хранится в коде сервиса и является источником генерации серверного кода и клиентов.

resources/openapi
├── index.yaml
├── common_parameters.yaml
├── offers
│ ├── offers_paths.yaml
│ └── schemas
│ └── offers.yaml
└── products
├── products_paths.yaml
└── schemas
└── products.yaml

resources/openapi/index.yaml — главный файл спецификации. Параметры генерации — в .ensi-openapi-gen.yaml в корне сервиса.

Генерация серверных заготовок — CLI ensi-gog server (подробнее: генератор ensi-gog):

elc ensi-gog server
elc mise run check:generated

Ожидаемые артефакты:

АртефактКуда
API-роутыroutes/api_gen.go
Runtime action routesapp/common/http/actionrouter/actionrouter.go (при наличии таких URI)
Handlersapp/modules/{module}/handlers/
DTO / FormRequestapp/modules/{module}/dto/
Enum'ыapp/enums/
Фабрики запросовapp/modules/{module}/factories/
Тестовые заготовкиapp/modules/{module}/tests/

bootstrap и providers​

  • bootstrap/ — сборка приложения: список провайдеров, регистрация commands/jobs/kafka/schedule/seeders;
  • app/providers/ — провайдеры сервиса, в т.ч. providers/clients для HTTP-клиентов к другим сервисам Ensi;
  • config/ и config/ensi_clients/ — конфигурация приложения и внешних клиентов.

database​

Миграции и сидеры Goravel:

  • database/migrations — миграции с регистрацией в bootstrap/migrations.go;
  • database/seeders — сидеры с регистрацией в bootstrap/seeders.go.

Тесты​

Подробнее об организации файлов и типичных сценариях — рекомендации к написанию автотестов на Go.

Общие инструменты OpenAPI/component-тестов лежат в tests/ в корне сервиса. Компонентные тесты модулей — рядом с кодом: app/modules/{module}/tests/{resource}/.

Запуск:

elc go test ./... --env=.env.testing

Соответствие PHP-структуре​

PHP (Laravel)Go (Goravel)
app/Domain/{Domain}/Actionsapp/modules/{module}/actions
app/Domain/{Domain}/Modelsapp/adapters/db
app/Http/ApiV1/Modules/{Module}/Controllersapp/modules/{module}/handlers
.../Requests + .../Resourcesapp/modules/{module}/dto + mappers
.../Queriesapp/modules/{module}/queries
public/api-docs/v1resources/openapi
app/Http/ApiV1/routes.php (generated)routes/api_gen.go