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 (29 типов ID, с UnmarshalJSON валидацией)
│   ├── services/            # Domain сервисы (чистая логика)
│   ├── repositories/        # Интерфейсы репозиториев
│   ├── interfaces/          # Интерфейсы внешних зависимостей (cache, config, logger, http, scheduler, platform, bots, media, uow, event_bus, ...)
│   ├── 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 (20), 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/         # Клиенты внешних сервисов (Bazaar, Discord, FB Login, GalaxyStore, GameCenter, Myket, OIDC, PlayGames)
│   │   └── providers/       # ~30 провайдеров платформ + registry.go (например VK, OK, Yandex, FB, TG, Apple, Google)
│   ├── bots/                # Telegram и др. боты
│   ├── cloudflare/          # Cloudflare Stream API
│   ├── tasks/               # Cron tasks
│   │   ├── factory.go
│   │   ├── runtime/         # Общие зависимости задач (config, http)
│   │   └── implementations/
│   ├── scheduler/           # gocron + RunTracker (из shared/cron) + LeaderChecker (реализация — shared/leader)
│   ├── rival/               # Rival policy engine
│   ├── trafficflow/         # TrafficFlow callback gateway
│   └── observability/       # Метрики, мониторинг
│       └── metrics/         # Prometheus (cache, controllers, db, http, tasks)
│
├── interfaces/               # HTTP-слой (Gin)
│   └── api/
│       ├── apierrors/       # Маппинг доменных ошибок → HTTP
│       ├── common/          # RouterGroup — обёртка над gin с BeforeUse()
│       ├── controllers/     # HTTP контроллеры
│       │   ├── v1/         # API v1 (legacy, POST-based)
│       │   ├── v2/         # API v2 (основной, тоже POST-based)
│       │   ├── acp/        # Admin Control Panel
│       │   ├── ext/        # External webhooks
│       │   └── api/        # Системный API
│       ├── merchants/       # Merchant handlers
│       ├── middlewares/     # error_handler, log_request, metrics, rate_limit, server_id, cf_country_code_proxy
│       └── router/          # Роутинг: v1, v2, acp, ext (SetupRoutes)
│
├── modules/                  # Модульная система (20 модулей, config-driven)
│   ├── factory.go           # Factory для создания модулей
│   ├── manager.go           # Менеджер жизненного цикла (идемпотентный)
│   ├── types/               # ModuleType (re-export из application/interfaces)
│   └── implementations/     # 20 реализаций модулей
│
├── pkg/                      # Shared packages
│   ├── engine/              # Engine: boot, gin (в т.ч. CORS для dev), lifecycle, prometheus
│   ├── cachekeys/           # Построение/нормализация ключей кэша (keys.go)
│   ├── payloadcheck/        # Проверка, примет ли Postgres payload как jsonb (UTF-8, NUL-байты)
│   ├── runtime/             # Shared singletons
│   │   └── httpclient/      # Thread-safe HTTP client (sync.RWMutex)
│   └── utils/               # Crypto-фасад над shared/cryptox + 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 ​

29 типов типобезопасных идентификаторов в domain/models/vo/ (генерируются gen_ids.go — генератор под тегом //go:build ignore, запускается через go run):

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
├── event_dlq_id.go         - EventDlqId
├── traffic_flow_id.go      - TrafficFlowId
├── traffic_flow_entry_id.go - TrafficFlowEntryId
└── rival_id.go             - RivalId

System Entities:
├── experiment_id.go        - ExperimentId
├── remote_config_id.go     - RemoteConfigId
├── cron_task_id.go         - CronTaskId
├── alert_id.go             - AlertId
├── alert_delivery_log_id.go - AlertDeliveryLogId
└── ad_id.go                - AdId

Clan Entities:
├── clan_id.go                    - ClanId
├── clan_join_request_id.go       - ClanJoinRequestId
├── clan_moderation_action_id.go  - ClanModerationActionId
├── clan_war_id.go                - ClanWarId
├── clan_war_phase_id.go          - ClanWarPhaseId
├── clan_war_pvp_id.go            - ClanWarPvpId
├── clan_war_reward_id.go         - ClanWarRewardId
└── clan_war_final_id.go          - ClanWarFinalId

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)

Version Vector (Optimistic Concurrency) ​

Тип VersionVector (domain/models/version_vector.go) — map[string]uint64, используется для оптимистичного контроля конкурентности при обновлении data пользователя. Ключ "server" зарезервирован для серверных правок через ACP.

Функции: ParseVersionVector, CompareVersionVectors, MergeMaxVersionVectors, IncrementServer. Ошибки конфликтов: domain/errors/version_conflict.go → статус-коды 3001-3005 (V1) / 409 Conflict (V2).

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
// 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, Experiment.

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 - error_handler, log_request, metrics, rate_limit, server_id, cf_country_code_proxy. Аутентификация (Auth/RequireUser) реализована методами базового контроллера и подключается как middleware. Permissive CORS для dev-режима живёт в pkg/engine/gin.go (включается флагом app.security.enable_cors).
  • 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
}

// 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 (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 UserRoleChangedEvent struct {
    BaseDomainEvent
    ApiType       shared.ApiType
    ApiUID        string
    OldRole       int
    NewRole       int
    OperatorEmail string
}

// domain/event_bus/handlers/user.go
func UserRoleChangedCacheHandler(userService UserCacheInvalidator, logger interfaces.Logger) EventHandler {
    // Обработка события
}

// Регистрация в container: application/container/container_events.go
syncAdapter.bus.Subscribe(events.EventTypeUserRoleChanged, userRoleCacheHandler)

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

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

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

МодульФайлОписание
Useruser.goПользователи и профили
Worldworld.goИгровые миры
Transactiontransaction.goIAP транзакции
Messagemessage.goСистема сообщений
Streamstream.goLivestream функционал
Eventevent.goИгровые события
TrafficFlowtraffic_flow.goОфферная механика
Rivalrival.goСоперники
Experimentexperiment.goЭксперименты
RemoteConfigremote_config.goУдалённые конфиги
Adsads.goРеклама
Alertalert.goСистема оповещений
Productproduct.goКаталог продуктов
Merchantmerchant.goMerchant хендлеры
Platformplatform.goPlatform хендлеры
Botsbots.goTelegram и др. боты
CronTaskcron_task.goПланировщик
Systemsystem.goСистемные функции
Orderorder.goЗаказы (SKU ↔ транзакция)
Clanclans.goКланы: ростер, модерация, война 5×5 (v2 + acp)

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

Модули загружаются из конфига 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.
  • SystemModuleSet — множество системных модулей (используется при формировании ModuleInfoList для ACP).

Контракты модулей (interfaces/api/router/module_provider.go) ​

Интерфейсы, по которым роутер находит контроллеры модуля — по одному на API-слой:

go
type V1ModuleProvider interface {
    GetV1Controllers() []func(*v1.Controller) any
}
type V2ModuleProvider interface { ... }
type AcpModuleProvider interface { ... }
type ExtModuleProvider interface { ... }

Возвращаемое значение — any; при регистрации роутер приводит его к router.Registrable (RegisterRoutes(*common.RouterGroup)), и значение, не прошедшее приведение, молча пропускается.

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

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 транзакция проверена
TrafficFlowCompletedEventSyncОффер завершён
UserDataChangedEventSyncДанные пользователя изменены из ACP
UserRoleChangedEventSyncРоль пользователя изменена из ACP
WorldDataChangedEventAsyncДанные мира изменены из ACP

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

Все подписки собраны в одном месте — application/container/container_events.go:

go
func (c *Container) setupEventHandlers() {
    syncAdapter := c.eventBus.(*eventBusAdapter)
    asyncAdapter := c.asyncEventBus.(*eventBusAdapter)

    iapVerifiedHandler := handlers.IapVerifiedTransactionHandler(c.transactionService, c.transactionService, c.domainLogger)
    syncAdapter.bus.Subscribe(events.EventTypeIapVerified, iapVerifiedHandler)

    worldDataBackupHandler := handlers.WorldDataChangedBackupHandler(c.WorldBackupService(), c.domainLogger)
    asyncAdapter.bus.Subscribe(events.EventTypeWorldDataChanged, worldDataBackupHandler)
}

Шина выбирается по издателю: обработчик подписывают на ту же шину, через которую событие публикуют. Например, бэкап пользовательских данных живёт на синхронной шине, потому что единственный издатель user.data_changed — ACP-контроллер пользователей, подключённый к container.EventBus(); на асинхронной шине он бы не срабатывал никогда.


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

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


Data Flow ​

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

1. HTTP Request
   ↓
2. Middleware chain (interfaces/api/middlewares/*)
   - error_handler, log_request, metrics, rate_limit, server_id, cf_country_code_proxy
   - Аутентификация (Auth/RequireUser — методы базового контроллера)
   ↓
3. Controller (interfaces/api/controllers/v2/worlds.go)
   - Валидация JSON
   - Создание DTO
   ↓
4. Application Service (application/services/world.go)
   - Оркестрация
   - Вызов domain сервисов
   ↓
5. Domain Service (domain/services/world.go)
   - Бизнес-логика
   - Валидация invariants
   ↓
6. Repository (интерфейс из domain/repositories)
   - Cache Decorator (infrastructure/repositories/decorators/world_cached.go)
     оборачивает postgres-репозиторий (паттерн decorator):
     на write — инвалидирует кэш и делегирует записи;
     на read — сначала кэш, при промахе — postgres
   - Postgres-репозиторий (infrastructure/repositories/postgres/world.go):
     конвертация Domain → ORM, SQL-запрос
   ↓
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: текущий оператор, привязанные аккаунты, версия сервера, модули, платформы, маршруты.

Structured Logging ​

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

Alert система ​

Механика оповещений (шаблоны, маршрутизация, провайдеры доставки) вынесена в общий модуль gitlab.hgpoint.com/servers/shared — пакеты shared/alerts и shared/alerts/providers. В MARV остаётся только локальный cron-диспетчер.

Из shared/alerts:

КомпонентОписание
Config / LoadConfigDestinations (куда отправлять), Templates (как форматировать)
MatchesDestination()Фильтрация destination по severity и tags
TemplateRendererРендер текста алерта по шаблону провайдера

Провайдеры доставки (shared/alerts/providers):

  • HelmProvider — структурированные алерты в HELM Alert Center (POST /ext/alerts/ingest, Bearer-авторизация), авто-обогащение тегами (source:<source>, server:<id>, version, env); для MARV source = "marv"
  • SlackProvider — webhook
  • TelegramProvider — Bot API

Локальный диспетчер: infrastructure/tasks/implementations/alert_dispatcher.go — cron task; читает конфиг через alerts.LoadConfig, выбирает destinations (selectDestinations → MatchesDestination), рендерит сообщение и отправляет через провайдеры доставки.


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

Cache Strategy ​

Кэш:

  1. Redis - distributed

TTL политики:

  • По умолчанию TTL берётся из infrastructure.redis.store_ttl — его применяет Set() стора. На нём живут кэши пользователей, миров, remote configs, сообщений, стримов, соперников, traffic flow, транзакций и экспериментов.
  • События: events:active / events:lookback — 1 час, events:starting (диапазон дат) — 24 часа.
  • Кланы — 30 секунд, клановые войны — 15 секунд: эти чтения делят один кэш с мутационными путями, поэтому TTL намеренно короткий.
  • Очередь ручных запусков планировщика — 10 минут.

Database Optimization ​

Индексы:

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

Connection Pooling:

yaml
infrastructure:
  postgres:
    pool:
      max_open: 20
      max_idle: 10
      timeout: 300s
    # Необязательная read-реплика со своим пулом: infrastructure.postgres.read.pool.*

timeout — это ConnMaxLifetime. Ключи читает infrastructure/database/factory.go; рекомендации по размеру пула под нагрузку — в config/config.example.yml.


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

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

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 — запросы без актуального токена блокируются

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

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

МетодФормулаНазначение
VerifyApiTokenMD5(apiType + apiUID + secret)V2: поле api_token в теле POST /v2/login и при привязке логина (/v2/identities/connect). Применяется default-платформами без собственной верификации.
VerifyAuthTokenMD5(secret_apiType_apiUID)V1 (deprecated): per-request заголовок X-Auth-Token (вместе с X-Auth-Type / X-Auth-Uid).

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

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

ACP:

  • HELM service token (Authorization: Bearer <RS256 JWT>, issuer helm) — верифицируется по HELM JWKS (acp.jwks, синкается задачей helm_certs); per-env доступ берётся из claim access["<slug>/<env>"] по ключу acp.access_key.

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 автоматически очищаются.

Авторизация ​

ACP: битовая маска прав. Права оператора — целое число; каждый модуль занимает один бит (interfaces/api/controllers/acp/access/access.go): AccessExperiment (1), AccessEvent (2), AccessMessage (4), AccessUser (8), AccessRemoteConfig (16), AccessCronTask (32), AccessStream (64), AccessTrafficFlow (128), AccessAds (256), AccessAlerts (512), AccessTransaction (1024), AccessClan (2048).

Маска приезжает в claim HELM-токена и попадает в shared.Operator.Access. Группы роутов помечаются нужным битом, а middleware Controller.CheckAccess() сверяет его с маской оператора:

go
// interfaces/api/controllers/acp/clans.go
secured := moduleRouter.Group("").BeforeUse(WithControllerRole(access.AccessClan))

Роуты вне secured-группы (обычно чтение — list, get) доступны любому оператору. Исключение — /acp/restrictions/issue и /acp/restrictions/lift: бит зависит от области ограничения (global — AccessUser, clans* — AccessClan), поэтому его проверяет обработчик.

Игроки: ограничения аккаунта. Баны и прочие ограничения игрока — строки user_restrictions на аккаунт (область, режим block/shadow, срок); глобальный блок проверяют middleware авторизации V1/V2 и /v2/login, остальные области — модули, которым они принадлежат. Подробно — Ограничения аккаунта.

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

В CI и линтере включены: staticcheck, ineffassign, errcheck, gosec (уязвимости), bodyclose (утечка HTTP-ресурсов), noctx (пропущенный context) и depguard (в т.ч. запрет доменному слою импортировать infrastructure/interfaces/application). Сканирование зависимостей: govulncheck.