Коротко: однозначного «правильного» шаблона нет — важно выбрать понятную и поддерживаемую модель разделения ответственности и последовательно её применять. Ниже — практическое руководство, которое поможет описать и построить архитектуру RESTful API на Go так, чтобы она была тестируемой, расширяемой и удобной в сопровождении.
1) Высокий уровень: какие архитектурные стеки бывают (и почему выбрать)
- Слойная (3‑слойная): handlers → services → repositories. Простая и понятная.
- Hexagonal / Ports-and-Adapters / Clean / Onion: чётко отделяет внешний мир (HTTP, DB, внешние API) от ядра бизнес‑логики (use cases). Лучше для больших проектов и облегчает тестирование и замену адаптеров.
Рекомендация: для небольших приложений достаточно слоёной архитектуры; для среднего/крупного — hexagonal/clean.
2) Разделение ответственности (роли)
- Transport/Handlers (HTTP): принимают запросы, валидируют вход, вызывают use‑case/service, формируют HTTP‑ответы.
- Use cases / Services: бизнес‑логика, оркестрация, трансакции.
- Repositories / Gateways / Adapters: доступ к БД, очередям, сторонним сервисам.
- Models / Entities / DTOs: сущности домена и структуры для передачи данных.
- Cross‑cutting: логирование, конфигурация, трассировка, аутентификация, rate limiting, метрики — реализуются как middleware/инструменты и уровни инфраструктуры.
- API‑документация: OpenAPI/Swagger.
3) Где размещать logging, config, routing (общая практика)
- Конфигурация: главный объект config загружаете в самом начале (main), передаёте в слои, либо используете DI/конструкторы. Используйте env/файлы/флаги (12‑factor). Библиотеки: viper, envconfig, koanf или простая загрузка из os.Getenv.
- Логирование: инициализируете логгер в main и передаёте в зависимости (или держите в package logger с возможностью инъекции). Используйте структурированные логгеры: zap, zerolog, logrus (zap/zerolog — эффективнее). Логи должны быть контекстно‑осознанными: request_id, user_id и т.д.
- Роутинг: роутер и middleware на уровне transport. Router строится в одном месте (например, internal/app/router) с регистрацией хендлеров из модулей. Подрутеры/группы для versioning (/api/v1/...), auth и т.п.
4) Рекомендованная последовательность инициализации (main)
1. Загрузить конфигурацию.
2. Настроить логгер и tracer (OpenTelemetry).
3. Подключить БД/очереди/кэш.
4. Создать репозитории/адаптеры.
5. Создать сервисы / use cases (передать репозитории, конфиг, логгер).
6. Создать HTTP хендлеры (передать сервисы, валидаторы, логгер).
7. Настроить роутер и middleware (логирование запросов, request_id, recover, timeouts, metrics, CORS).
8. Запустить HTTP сервер с graceful shutdown.
Так вы получаете явные зависимости и легко тестируете слои подменой интерфейсов.
5) Middleware — что туда логично положить
- request id (X-Request-ID)
- structured request logging (method, path, status, latency)
- recovery (panic -> 500)
- authentication/authorization
- timeouts / context cancellation
- metrics (Prometheus)
- CORS, rate limiting
Middleware должны быть независимы и порядка имеет значение (например, request-id -> logging -> auth -> handler).
6) Пример структуры проекта (вариант для internal пакета)
- cmd/app/main.go
- internal/config/ — парсинг конфигов
- internal/logger/ — вспом. логика, wrapper над zap/zerolog
- internal/server/ — http server bootstrap, graceful shutdown
- internal/router/ — сборка маршрутов, middlewares
- internal/handlers/ — http handlers (по пакетам: users, auth, posts)
- internal/services/ — бизнес-логика (use cases)
- internal/repository/ — db адаптеры, интерфейсы
- internal/models/ — domain/entities, DTOs
- migrations/ — db миграции
- api/ — OpenAPI spec
- pkg/ (опционально) — общие утилиты, которые можно публично экспортировать
7) Пример "высокого уровня" кода инициализации (псевдо-Go)
- load config
- logger := logger.New(cfg.Log)
- db := db.Open(cfg.DB)
- userRepo := repository.NewUserRepo(db)
- userSvc := services.NewUserService(userRepo, logger)
- userHandler := handlers.NewUserHandler(userSvc, logger)
- router := router.New()
- router.Group("/api/v1").RegisterUserRoutes(userHandler)
- srv := server.New(cfg.Server, router)
- srv.StartGraceful()
(Важно: использовать конструкторы для всех зависимостей, не обращаться к глобальным переменным)
8) Ошибки и их перевод в HTTP
- Определите набор domain ошибок (NotFound, ValidationError, Unauthorized, Conflict, InternalError).
- Пользуйтесь обёртками/типами ошибок, которые можно распознать и перевести в соответствующий HTTP статус в одном месте (уже в transport/handler или middleware).
- Возвращайте структурированные ошибки клиенту (код, message, details).
9) Логирование и трассировка
- Структурированные JSON‑логи. Полезные поля: ts, level, msg, request_id, path, method, status, duration, user_id.
- Интеграция с OpenTelemetry (traces) и Prometheus (metrics). Трейсы и логи должны быть коррелированы по request_id/trace_id.
- Старайтесь логировать на уровне middleware и сервисов (не в каждом мелком пакете).
10) Конфигурации и секреты
- Конфиг как структура с поддержкой env override + config file. Secrets отдельно (vault/secret manager/k8s secrets).
- Не храните секреты в репозитории.
11) Тесты
- Unit: мокать репозитории при тестировании сервисов.
- Integration: тесты HTTP‑уровня через httptest server + тестовая БД (docker, testcontainers, sqlite in‑memory).
- E2E: прогонять наиболее важных сценарий против реальной среды.
12) Документация архитектуры (как правильно описывать)
- Компонентная диаграмма: слои/модули, внешние зависимости (DB, auth, queue), и связи.
- Sequence диаграммы для ключевых сценариев (login, create resource).
- ERD для БД.
- API spec (OpenAPI) + примеры запросов.
- ADR (Architectural Decision Records) — зачем выбран подход (hexagonal vs layered, выбор логгера, миграций и т.д.).
- README с описанием запуска, переменных окружения, миграций, тестов.
13) Конкретные рекомендации по библиотекам (Go)
- Router: chi (легковесный, middleware), gorilla/mux, fiber (если нужен другой подход). Chi хорошо сочетается с clean architecture.
- Logger: zap или zerolog.
- Config: viper, koanf, envconfig (просто map env -> struct).
- DB: database/sql + sqlx / gorm (ORM) / ent (type‑safe).
- Tracing/metrics: OpenTelemetry + Prometheus client.
14) Пример проблем и как их решать
- «Куда поместить middleware?»: в слой transport/роутер, чтобы любой запрос проходил через единый набор.
- «Где логировать ошибки бизнес‑логики?»: логирование проводится в сервисах и middleware; но в handlers — конвертация в HTTP‑ответы. Избегайте двойного логирования одного и того же события.
- «Глобальные пакеты vs DI»: избегайте глобальных переменных для логгера/конфигов — делают тестирование и сопровождение сложнее. Используйте конструкторы и интерфейсы.
15) Небольшой чеклист при описании архитектуры
- Чётко опишите слои и их ответственность.
- Покажите поток данных: HTTP -> middleware -> handlers -> services -> repositories -> DB.
- Опишите cross‑cutting concerns (где логирование, где аутентификация, где метрики).
- Приведите примеры точек инициализации и shutdown.
- Укажите выбор ключевых библиотек с причинами.
- Добавьте диаграммы и ADR'ы.
Если нужно, могу:
- Нарисовать конкретную диаграмму для вашего проекта (перечислите компоненты).
- Предложить файл‑структуру и пример main.go, исходя из вашего кода.
- Подготовить шаблон README/ADR для документации архитектуры.
Хочешь, я составлю конкретную структуру и пример main.go/роутинга под твой проект (опиши, какие компоненты/БД/внешние сервисы у тебя есть)?