Skip to content

Архитектура MARV

Оглавление

  1. Обзор архитектуры
  2. Структура проекта
  3. Domain-Driven Design
  4. Слои приложения
  5. Ключевые паттерны
  6. Модули системы
  7. Обработка событий
  8. Управление конфигурацией

Обзор архитектуры

MARV построен на принципах Domain-Driven Design (DDD) с чётким разделением на слои.

Архитектурные принципы

  1. Layered Architecture - чёткое разделение ответственности
  2. Dependency Inversion - зависимости направлены к domain
  3. Value Objects - типобезопасные идентификаторы
  4. Event-Driven - асинхронная обработка через EventBus
  5. Repository Pattern - абстракция доступа к данным
  6. Factory Pattern - создание сложных объектов

Структура проекта

marv/
├── cmd/                      # Точки входа (бинарники)
│   ├── marv/                # Основной сервер
│   │   └── main.go
│   ├── hgoose/              # Утилита миграций БД (goose v3)
│   └── config-migrator/     # Миграция legacy-конфигов

├── domain/                    # Ядро бизнес-логики (не зависит ни от чего)
│   ├── models/               # Domain модели
│   │   └── vo/              # Value Objects (23 типа ID, с UnmarshalJSON валидацией)
│   ├── services/            # Domain сервисы (чистая логика)
│   ├── repositories/        # Интерфейсы репозиториев
│   ├── interfaces/          # Интерфейсы для внешних зависимостей (cache, config)
│   ├── event_bus/           # Event Bus (sync + async, DLQ, retry)
│   ├── errors/              # Domain ошибки (коды + sentinel)
│   ├── iap/                 # Контракты In-App Purchase
│   ├── platform/            # Контракты платформ (VK, OK, TG, FB, ...)
│   └── shared/              # Общие типы (ApiType, ApiTypeRange, RawExpr, stats)

├── application/              # Оркестрация use-cases
│   ├── container/           # DI Container (split: container, getters, events, ops)
│   ├── dto/                 # Data Transfer Objects
│   ├── mappers/             # Domain ↔ DTO преобразования
│   ├── services/            # Application-сервисы (OnlineTracker и др.)
│   └── interfaces/          # ModuleType (19), CoreModules, SystemModuleSet,
│                            # Controller, StatsProvider (source of truth)

├── infrastructure/           # Внешние зависимости (PostgreSQL, Redis, HTTP)
│   ├── database/            # PostgreSQL (GORM) + mapper + ORM модели
│   ├── cache/               # Redis + MemoryStore (тесты)
│   ├── config/              # Koanf + JSON Schema валидация
│   ├── logger/              # Zerolog (file + stdout)
│   ├── http/                # HTTP-клиент + retry
│   ├── repositories/        # GORM-реализации репозиториев
│   ├── platform/            # Интеграции платформ
│   │   ├── clients/         # Discord, GalaxyStore, Myket, Bazaar
│   │   └── providers/       # VK, OK, Yandex, FB, TG, Apple, Google
│   ├── bots/                # Telegram и др. боты
│   ├── cloudflare/          # Cloudflare Stream API
│   ├── tasks/               # Cron tasks
│   │   ├── factory.go
│   │   └── implementations/
│   ├── scheduler/           # gocron + taskrunner + RunTracker (in-memory stats)
│   ├── leader/              # Redis-based leader election
│   ├── rival/               # Rival policy engine
│   ├── trafficflow/         # TrafficFlow callback gateway
│   └── observability/       # Метрики, мониторинг
│       ├── metrics/         # Prometheus (cache, controllers, db, http, tasks)
│       └── system/          # HostInfo (platform, kernel, uptime)

├── interfaces/               # HTTP-слой (Gin)
│   └── api/
│       ├── apierrors/       # Маппинг доменных ошибок → HTTP
│       ├── common/          # RouterGroup — обёртка над gin с BeforeUse()
│       ├── controllers/     # HTTP контроллеры
│       │   ├── v1/         # API v1 (legacy, POST-based)
│       │   ├── v2/         # API v2 (основной, RESTful)
│       │   ├── acp/        # Admin Control Panel
│       │   ├── ext/        # External webhooks
│       │   └── api/        # Системный API
│       ├── merchants/       # Merchant handlers
│       ├── middlewares/     # Auth, CORS, rate limit, body limit, timeout, metrics
│       └── router/          # Роутинг: v1, v2, acp, ext (SetupRoutes)

├── modules/                  # Модульная система (19 модулей, config-driven)
│   ├── contracts.go          # Типизированные контракты (V1/V2/Acp/Ext Module)
│   ├── factory.go           # Factory для создания модулей
│   ├── manager.go           # Менеджер жизненного цикла (идемпотентный)
│   ├── types/               # ModuleType (re-export из application/interfaces)
│   └── implementations/     # 19 реализаций модулей

├── internal/                 # Внутренние зеркальные реализации
│   ├── infrastructure/      # Декораторы, доп. реализации
│   │   ├── http/            # Внутренний HTTP
│   │   ├── platform/        # Внутренние платформы
│   │   ├── repositories/    # Cache-декораторы (decorator pattern)
│   │   ├── rival/           # Rival policy
│   │   ├── scheduler/       # Внутренний scheduler
│   │   ├── tasks/           # Внутренние задачи
│   │   └── trafficflow/     # Внутренний TrafficFlow
│   └── observability/       # Внутренние метрики

├── pkg/                      # Shared packages
│   ├── engine/              # Engine: boot, gin, lifecycle, prometheus
│   ├── alerts/              # Alert система (Manager, Templates, Providers)
│   │   ├── manager.go       # Emit/Render, template cache (sync.RWMutex)
│   │   ├── config.go        # Destinations, Templates, Clone()
│   │   ├── event.go         # Event → Alert mapping
│   │   ├── template.go      # Go text/template renderer
│   │   └── providers/       # Delivery providers
│   │       ├── helm.go      # → HELM Alert Center (structured + tag enrichment)
│   │       ├── slack.go     # → Slack webhook
│   │       └── telegram.go  # → Telegram Bot API
│   ├── logutil/             # SanitizeURL/Headers/Map
│   ├── runtime/             # Shared singletons
│   │   └── httpclient/      # Thread-safe HTTP client (atomic.Value)
│   └── utils/               # Crypto (MD5, SHA1, SHA256, HMAC, SecureCompare), OAuth

├── config/                   # Конфигурационные файлы
│   ├── config.example.yml   # Пример конфигурации
│   ├── .env.example         # Пример переменных окружения (MARV__*)
│   ├── bots.example.json    # Пример конфигурации ботов
│   └── products.example.json # Пример каталога продуктов

├── scripts/                  # Вспомогательные скрипты
│   └── deploy.sh           # Помощник деплоя (migrate, run, config-migrate)

├── migrations/               # SQL-миграции (goose v3)
├── docs/                     # Документация (OpenAPI, VitePress, Redocly)
├── tests/                    # Интеграционные тесты

├── Dockerfile               # Multi-stage build (marv + hgoose + config-migrator)
├── docker-compose.yml       # Локальная разработка (app + Postgres + Redis)
├── Makefile                 # Сборка, линт, тесты, бандлы
└── .gitlab-ci.yml           # CI/CD (lint → test → security → build/release)

Domain-Driven Design

Value Objects

23 типа типобезопасных идентификаторов в domain/models/vo/:

Core Entities:
├── user_id.go              - UserId
├── world_id.go             - WorldId
├── message_id.go           - MessageId
├── transaction_id.go       - TransactionId
├── stream_id.go            - StreamId
├── stream_input_id.go      - StreamInputId
├── event_id.go             - EventId
├── event_result_id.go      - EventResultId
├── traffic_flow_id.go      - TrafficFlowId
├── traffic_flow_entry_id.go - TrafficFlowEntryId
└── rival_id.go             - RivalId

System Entities:
├── ab_test_id.go           - AbTestId
├── remote_config_id.go     - RemoteConfigId
├── cron_task_id.go         - CronTaskId
├── alert_id.go             - AlertId
├── alert_delivery_log_id.go - AlertDeliveryLogId
├── acp_user_id.go          - AcpUserId
└── ad_id.go                - AdId

Backup Entities:
├── user_backup_id.go       - UserBackupId
├── world_backup_id.go      - WorldBackupId
└── user_data_update_id.go  - UserDataUpdateId

Пример использования:

go
// Правильно - невозможно перепутать параметры
func CreateMessage(userID UserId, worldID WorldId, eventID EventId)

// Неправильно - легко перепутать
func CreateMessage(userID int64, worldID int64, eventID int64)

Domain Abstractions (domain/shared)

RawExpr — абстракция для raw SQL выражений, позволяющая domain-сервисам формировать SQL-выражения (например, counter + 1) без импорта gorm. Infrastructure-слой конвертирует RawExpr в gorm.Expr:

go
// domain — формирует выражение
updates["execution_count"] = shared.NewExpr("execution_count + ?", 1)

// infrastructure — конвертирует для GORM
if expr, ok := v.(shared.RawExpr); ok {
    out[k] = gorm.Expr(expr.SQL, expr.Vars...)
}

UpdateMap Pattern (GORM)

Для обновления записей в PostgreSQL все репозитории используют паттерн UpdateMap — явное формирование map[string]any вместо передачи ORM-структуры в GORM.Updates(). Это необходимо, потому что Updates(struct) игнорирует zero-value поля (false, 0, ""):

go
// internal/infrastructure/database/mapper/cron_task.go
func CronTaskUpdateMap(m *models.CronTask) map[string]any {
    return map[string]any{
        "enabled":    m.Enabled,    // false корректно сохранится
        "name":       m.Name,
        "expression": m.Expression,
        // ...
    }
}

// infrastructure/repositories/postgres/cron_task.go
func (r *repo) Update(ctx context.Context, task *models.CronTask) error {
    return r.db.Model(&orm.CronTaskRecord{}).
        Where("id = ?", task.ID).
        Updates(dbmapper.CronTaskUpdateMap(task)).Error
}

Этот паттерн реализован для всех сущностей: CronTask, Ad, Alert, RemoteConfig, Stream, Event, Message, TrafficFlow, AbTest.

Rich Domain Models

Модели содержат бизнес-логику, а не только данные:

go
// Rich Model
type User struct {
    ID     UserId
    Banned bool
    // ...
}

func (u *User) Ban() { u.Banned = true }
func (u *User) Unban() { u.Banned = false }
func (u *User) IsBanned() bool { return u.Banned }
func (u *User) CanCreateWorld() bool { return !u.IsBanned() }

Модели с rich behavior:

  • User - ban/unban, attach to ACP, session management
  • World - IsDefault(), BelongsToUser()
  • Message - IsCompleted(), MarkAsCompleted(), IsExpired()
  • Stream - IsFull(), CanAddInput()
  • Event - IsActive(), IsFinished(), CanAddResult()

Слои приложения

1. Domain Layer (Ядро)

Ответственность: Бизнес-логика, инварианты, правила

Компоненты:

  • Models - сущности и value objects
  • Services - сложная domain логика (координация entities)
  • Repositories - интерфейсы (не реализации!)
  • Events - доменные события
  • Errors - доменные ошибки

Правила:

  • НЕ зависит от других слоёв
  • НЕ знает о БД, HTTP, кэше
  • Только чистая бизнес-логика
  • Только Go стандартная библиотека + domain dependencies

Пример:

go
// domain/services/user.go
func (s *UserService) BanUser(ctx context.Context, userID UserId, reason string) error {
    user, err := s.repo.GetByID(ctx, userID)
    if err != nil {
        return err
    }
    
    // Бизнес-логика в domain
    user.Ban()
    
    // Сохранение через интерфейс
    return s.repo.Update(ctx, user)
}

2. Application Layer (Оркестрация)

Ответственность: Use cases, координация domain объектов

Компоненты:

  • Services - оркестрация domain сервисов
  • Container - Dependency Injection
  • DTOs - передача данных между слоями
  • Mappers - преобразование Domain ↔ DTO

Правила:

  • Зависит от Domain
  • НЕ зависит от Infrastructure/Interfaces
  • Оркестрирует несколько domain сервисов
  • Управляет транзакциями

Пример:

go
// application/services/user.go
func (s *UserService) BanUserWithCleanup(ctx context.Context, dto *BanUserDTO) error {
    // Оркестрация нескольких domain операций
    if err := s.domain.BanUser(ctx, dto.UserID, dto.Reason); err != nil {
        return err
    }
    
    // Очистка связанных данных
    if err := s.domain.InvalidateSessions(ctx, dto.UserID); err != nil {
        return err
    }
    
    // Публикация события
    event := NewUserBannedEvent(dto.UserID, dto.Reason)
    return s.eventBus.Publish(ctx, event)
}

3. Infrastructure Layer (Реализации)

Ответственность: Внешние зависимости (БД, кэш, HTTP)

Компоненты:

  • Repositories - PostgreSQL реализации
  • Cache - Redis реализации
  • Config - Koanf конфигурация
  • Logger - zerolog логирование
  • Platform - интеграции с платформами

Правила:

  • Реализует интерфейсы из Domain
  • Конвертирует Domain ↔ ORM
  • Использует внешние библиотеки
  • НЕ содержит бизнес-логику

Пример:

go
// infrastructure/repositories/postgres/user.go
func (r *userRepository) GetByID(ctx context.Context, id UserId) (*models.User, error) {
    var record orm.UserRecord
    err := r.db.Where("id = ?", id.Int64()).First(&record).Error
    if err != nil {
        return nil, err
    }
    
    // Конвертация ORM → Domain
    return mapper.UserDomainFromRecord(&record), nil
}

4. Interfaces Layer (API)

Ответственность: HTTP endpoints, CLI

Компоненты:

  • Controllers - HTTP обработчики
  • Middlewares - аутентификация, CORS, metrics
  • Router - маршрутизация (Gin)

Правила:

  • Зависит от Application/Domain интерфейсов
  • Валидация входных данных
  • Преобразование HTTP ↔ DTO
  • НЕ содержит бизнес-логику

Пример:

go
// interfaces/api/controllers/v2/users.go
func (c *UsersController) BanUser(ctx *gin.Context) {
    var dto dto.BanUserDTO
    if err := ctx.ShouldBindJSON(&dto); err != nil {
        ctx.Error(apierrors.ErrInvalidInput.WithCause(err))
        return
    }
    
    // Делегирование в application layer
    if err := c.service.BanUser(ctx.Request.Context(), &dto); err != nil {
        ctx.Error(err)
        return
    }
    
    ctx.JSON(200, gin.H{"success": true})
}

Ключевые паттерны

1. Repository Pattern

Абстракция доступа к данным:

go
// domain/repositories/user.go (интерфейс)
type UserRepository interface {
    GetByID(ctx context.Context, id UserId) (*User, error)
    Create(ctx context.Context, user *User) error
    Update(ctx context.Context, user *User) error
}

// infrastructure/repositories/postgres/user.go (реализация)
type userRepository struct {
    db database.Store
}

// internal/infrastructure/repositories/decorators/user_cached.go (decorator)
type cachedUserRepository struct {
    inner repositories.UserRepository
    cache interfaces.CacheStore
}

2. Factory Pattern

Создание сложных объектов:

go
// modules/factory.go
type Factory struct {
    modules map[appinterfaces.ModuleType]func(*container.Container) appinterfaces.Module
}

func (f *Factory) Create(moduleType appinterfaces.ModuleType, container *container.Container) (appinterfaces.Module, error) {
    creator := f.modules[moduleType]
    return creator(container), nil
}

Используется для:

  • Модули (modules/factory.go)
  • Tasks (internal/infrastructure/tasks/factory.go)

3. Dependency Injection

Центральный контейнер:

go
// application/container/container.go
type Container struct {
    config        interfaces.ConfigStore
    db            database.Store
    cache         interfaces.CacheStore
    eventBus      interfaces.EventBus
    // ... сервисы
}

func (c *Container) Users() interfaces.UserService {
    c.once.Do(func() {
        c.users = services.NewUserService(...)
    })
    return c.users
}

4. Event-Driven Architecture

EventBus для async операций:

go
// domain/event_bus/events/user.go
type UserBannedEvent struct {
    UserID UserId
    Reason string
}

// domain/event_bus/handlers/user.go
type UserEventHandler struct {
    repo repositories.UserRepository
}

func (h *UserEventHandler) HandleUserBanned(ctx context.Context, event *events.UserBannedEvent) error {
    // Обработка события
    log.Info().Msg("User banned")
    return nil
}

// Регистрация в container
func (c *Container) setupEventHandlers() {
    handler := handlers.NewUserEventHandler(c.Users())
    c.eventBus.Subscribe("user.banned", handler.HandleUserBanned)
}

Модули системы

Модульная архитектура

19 модулей в modules/implementations/:

МодульФайлОписание
Useruser.goПользователи и профили
Worldworld.goИгровые миры
Transactiontransaction.goIAP транзакции
Messagemessage.goСистема сообщений
Streamstream.goLivestream функционал
Eventevent.goИгровые события
TrafficFlowtraffic_flow.goОфферная механика
Rivalrival.goСоперники
AbTestabtest.goA/B тестирование
RemoteConfigremote_config.goУдалённые конфиги
Adsads.goРеклама
Alertalert.goСистема оповещений
Productproduct.goКаталог продуктов
Merchantmerchant.goMerchant хендлеры
Platformplatform.goPlatform хендлеры
Botsbots.goTelegram и др. боты
CronTaskcron_task.goПланировщик
AcpUseracp_user.goACP пользователи
Systemsystem.goСистемные функции

Загрузка модулей

Модули загружаются из конфига app.modules (рекомендуется) или программно через engine.With...():

yaml
app:
  modules:
    - user
    - world
    - transaction
    - stream
    - event

Manager.Create() идемпотентен — дублирование безопасно при комбинации конфига и кода.

Core и System модули определены в application/interfaces/interfaces.go (source of truth) и re-export в modules/types/types.go:

  • CoreModules — модули, загружаемые всегда: system, user, cron_task, merchant, platform. acp_user добавляется, если включены ACP-маршруты.
  • SystemModuleSet — множество системных модулей (используется при формировании ModuleInfoList для ACP).

Контракты модулей (modules/contracts.go)

Типизированные интерфейсы модулей для каждого API-слоя:

go
type V1Module interface {
    appinterfaces.Module
    GetV1Controllers() []func(*v1.Controller) appinterfaces.Controller
}
type V2Module interface { ... }
type AcpModule interface { ... }
type ExtModule interface { ... }

Интерфейс модуля

go
type Module interface {
    Type() types.ModuleType
}

Модули реализуют дополнительные интерфейсы для предоставления контроллеров различных API-слоёв (GetV1Controllers(), GetV2Controllers(), GetAcpControllers(), GetExtControllers()). Роутеры опрашивают модули через Manager и регистрируют контроллеры.

Фазы:

  1. Create — Factory создаёт модуль по типу из конфига (app.modules) или программно
  2. Routes — Router'ы запрашивают контроллеры у модулей и регистрируют HTTP endpoints

Обработка событий

EventBus

Два типа шин:

go
// Синхронная (блокирующая)
c.eventBus = event_bus.NewInMemoryEventBus()

// Асинхронная (non-blocking, workers)
c.asyncEventBus = event_bus.NewAsyncEventBus(10)

События в системе

СобытиеТипОписание
IapVerifiedEventSyncIAP транзакция проверена
TrafficFlowCompletedEventAsyncОффер завершён
UserBannedEventAsyncПользователь забанен

Регистрация обработчика

go
// application/container/container.go
func (c *Container) setupEventHandlers() {
    // IAP handler
    iapHandler := handlers.NewIapVerificationHandler(
        c.Transactions(),
        c.Users(),
    )
    c.eventBus.Subscribe("iap.verified", iapHandler.Handle)
}

Управление конфигурацией

Подробное описание всех параметров конфигурации — см. Конфигурация.


Data Flow

Типичный запрос (создание world):

1. HTTP Request

2. Controller (interfaces/api/controllers/v2/worlds.go)
   - Валидация JSON
   - Создание DTO

3. Application Service (application/services/world.go)
   - Оркестрация
   - Вызов domain сервисов

4. Domain Service (domain/services/world.go)
   - Бизнес-логика
   - Валидация invariants

5. Repository (infrastructure/repositories/postgres/world.go)
   - Конвертация Domain → ORM
   - SQL запрос

6. Cache Decorator (internal/infrastructure/repositories/decorators/world_cached.go)
   - Инвалидация кэша

7. EventBus
   - Публикация WorldCreatedEvent

8. Response
   - Конвертация Domain → DTO
   - HTTP 201 Created

Тестирование

Структура тестов

domain/services/*_test.go       - Domain logic unit tests
application/services/*_test.go  - Application use cases tests
interfaces/api/controllers/*_test.go - HTTP handlers tests

Паттерн: Local Interfaces + Stubs

go
// interfaces/api/controllers/v2/users_test.go
type stubUserService struct {
    getUserResponse *models.User
    getUserError    error
}

func (s *stubUserService) GetUser(ctx, id) (*models.User, error) {
    return s.getUserResponse, s.getUserError
}

func TestUsersController_GetUser(t *testing.T) {
    service := &stubUserService{
        getUserResponse: &models.User{ID: 1},
    }
    
    controller := NewUsersController(service)
    // ... тестируем контроллер
}

Метрики и мониторинг

Prometheus метрики

POST /acp/system/metrics

Метрики:

  • HTTP requests (duration, count, errors)
  • DB queries (duration, count)
  • Cache operations (hits, misses)
  • Task executions (duration, success/failure)

Аналитика и мониторинг

POST /acp/system/overview

Сводная информация для ACP-дашборда: статистика пользователей (кэшируется задачей stats_snapshot), пользователи онлайн и сессии за день (Redis), состояние кластера, память, Redis INFO, статусы задач.

POST /acp/me

Точка входа ACP: текущий ACP-пользователь, привязанные аккаунты, версия сервера, модули, платформы, маршруты.

Structured Logging

go
// zerolog
log.Info().
    Str("user_id", userID.String()).
    Str("action", "ban").
    Msg("User banned")

Alert система

pkg/alerts/ — полноценная система оповещений с шаблонами и маршрутизацией:

КомпонентОписание
ManagerEmit (создание alert), Render (шаблонизация), кэширование шаблонов (RWMutex)
ConfigDestinations (куда отправлять), Templates (как форматировать), Clone()
EventСтруктура alert-события (Type, Severity, Title, Message, Tags, Payload)
MatchesDestination()Фильтрация по severity и tags

Providers:

  • HelmProvider — структурированные алерты в HELM Alert Center (/ext/alerts/ingest), авто-обогащение тегами (source:marv, server:ID, version, env)
  • SlackProvider — webhook
  • TelegramProvider — Bot API

Диспетчер: internal/infrastructure/tasks/implementations/alert_dispatcher.go — cron task, маршрутизирует алерты по destinations


Производительность

Cache Strategy

Кэш:

  1. Redis - distributed

TTL политики:

  • User data: 1 hour
  • Worlds: 30 minutes
  • Products: 24 hours
  • Remote configs: 5 minutes

Database Optimization

Индексы:

  • Composite indexes для cleanup операций
  • Covering indexes для частых запросов

Connection Pooling:

yaml
infrastructure:
  database:
    max_open_conns: 25
    max_idle_conns: 5
    conn_max_lifetime: 300s

Безопасность

Аутентификация

Client API (V2):

  • JWT-авторизация (Authorization: Bearer <JWT>, токен через POST /v2/login)
  • Single session (jti)

Client API (V1, deprecated):

  • Per-request платформенная авторизация через X-Auth-Token, X-Auth-Type, X-Auth-Uid
  • Single session mode (deprecated): при app.session.single_mode.enabled на /v1/users/fetch генерируется X-Set-Session-Token; при blocking: true — запросы без актуального токена блокируются

Верификация токенов платформ (V1):

Базовый Platform предоставляет дефолтную реализацию верификации, которую конкретные платформы могут переопределить:

МетодФормулаОписание
VerifyApiTokenMD5(apiType + apiUID + secret)Проверка X-Auth-Token при регистрации (первичный запрос).
VerifyAuthTokenMD5(secret_apiType_apiUID)Проверка auth-токена (формат secret_apiType_apiUID через _).

Где secret = app.security.secret из конфигурации, apiType = числовой ID платформы, apiUID = UID пользователя.

Платформы с собственной верификацией (VK, OK, Yandex, Facebook и др.) переопределяют эти методы. Платформы без переопределения (Amazon, App Store, Google Play, и т.д.) используют базовую реализацию.

ACP:

  • Google OAuth (Authorization: Bearer <Google-OAuth-JWT>)

External webhooks:

  • Signature verification
  • IP whitelisting (опционально)

Rate Limiting

In-memory token bucket (per-IP), конфигурируется через:

yaml
app:
  security:
    rate_limit:
      enabled: true
      rps: 100
      burst: 200

При превышении — HTTP 429. Старые записи IP автоматически очищаются.

Авторизация

Role-based access control:

go
router.WithControllerRole(access.RoleAdmin)
router.WithControllerRole(access.RoleModerator)

Статический анализ

В CI и линтере включены: gosec (уязвимости), bodyclose (утечка HTTP-ресурсов), noctx (пропущенный context). Сканирование зависимостей: govulncheck.