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 (22 типа ID, с UnmarshalJSON валидацией)
│   │   ├── user.go
│   │   ├── world.go
│   │   └── ...
│   ├── services/            # Domain сервисы (чистая логика)
│   ├── repositories/        # Интерфейсы репозиториев
│   ├── interfaces/          # Интерфейсы для внешних зависимостей
│   ├── event_bus/           # События и обработчики
│   ├── errors/              # Domain ошибки
│   └── shared/              # Общие типы (ApiType, cachekeys, RawExpr)

├── application/              # Оркестрация use-cases
│   ├── services/            # Application сервисы
│   ├── container/           # DI Container
│   ├── dto/                 # Data Transfer Objects
│   ├── mappers/             # Domain ↔ DTO преобразования
│   └── interfaces/          # Интерфейсы application слоя

├── infrastructure/           # Внешние зависимости (PostgreSQL, Redis, HTTP)
│   ├── database/            # GORM, миграции
│   │   └── postgres/        # PostgreSQL драйвер
│   ├── cache/               # Redis implementation
│   │   └── redis/
│   ├── repositories/        # Реализации репозиториев
│   │   └── postgres/        # PostgreSQL repositories
│   ├── config/              # Конфигурация (Koanf) + JSON Schema
│   ├── logger/              # Логирование (zerolog)
│   └── platform/            # Интеграции платформ

├── internal/                 # Внутренние реализации (private)
│   ├── infrastructure/
│   │   ├── database/
│   │   │   ├── mapper/      # ORM ↔ Domain преобразования
│   │   │   └── orm/         # GORM модели
│   │   ├── repositories/
│   │   │   └── decorators/  # Cache decorators
│   │   ├── tasks/           # Cron tasks система
│   │   │   ├── factory.go
│   │   │   └── implementations/
│   │   │       ├── data_clean.go
│   │   │       ├── alert_dispatcher.go
│   │   │       └── user_data_batch_update.go
│   │   ├── scheduler/       # Планировщик задач
│   │   └── cloudflare/      # Cloudflare Stream API
│   ├── alerts/              # Alert providers (Telegram, Slack)
│   └── observability/       # Метрики, мониторинг

├── interfaces/               # HTTP-слой
│   └── api/
│       ├── controllers/     # HTTP контроллеры
│       │   ├── v1/         # API v1 (deprecated)
│       │   ├── v2/         # API v2 (основной)
│       │   ├── ext/        # External webhooks
│       │   └── acp/        # Admin Control Panel
│       ├── middlewares/     # HTTP middleware (rate_limit, body_limit, timeout)
│       └── router/          # Роутинг (Gin)

├── modules/                  # Модульная система (config-driven)
│   ├── factory.go           # Factory для создания модулей
│   ├── manager.go           # Менеджер жизненного цикла (идемпотентный)
│   ├── implementations/     # Реализации модулей (19 шт)
│   │   ├── user.go, world.go, transaction.go, message.go
│   │   ├── stream.go, event.go, traffic_flow.go
│   │   ├── abtest.go, ads.go, alert.go
│   │   └── ...
│   └── types/               # Типы модулей

├── pkg/                      # Публичные пакеты
│   └── engine/              # Engine для запуска сервера

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

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

├── migrations/               # SQL-миграции (goose v3)
├── docs/                     # Документация

├── 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

Преимущества:

  • Невозможно перепутать типы ID на этапе компиляции
  • Type-safe операции
  • Легко расширять (добавлять domain методы)
  • Валидация при десериализации (UnmarshalJSON отклоняет отрицательные значения)

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

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...)
}

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
}

Преимущества:

  • Легко подменить реализацию (для тестов)
  • Cache decorator прозрачно добавляет кэширование
  • Единая точка доступа к данным

2. Factory Pattern

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

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

func (f *Factory) Create(moduleType types.ModuleType, container *container.Container) (interfaces.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() идемпотентен — дублирование безопасно при комбинации конфига и кода.

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

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)
}

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

Структура конфигов

config/
├── config.example.yml      # Пример конфигурации
├── .env.example            # Пример env-переменных (MARV__*)
├── config.debug.yml        # Dev режим
├── config.release.yml      # Production
├── config.test.yml         # Тесты
├── bots.debug.json         # Telegram боты (dev)
├── bots.release.json       # Telegram боты (prod)
└── products.json           # Каталог продуктов

JSON Schema валидация

infrastructure/config/koanf/schemas/
├── config.schema.json      # Основная схема
├── bots.schema.json        # Схема ботов
└── products.schema.json    # Схема продуктов

Environment Variables

Формат: MARV__SECTION__SUBSECTION__KEY (только main config)

bash
# Переопределение порта
MARV__APP__SERVER__PORT=9090

# Duration типы
MARV__DOMAIN__CLEANUP__DATA_RETENTION=4320h  # 180 дней

Duration типы вместо int

yaml
# Правильно
domain:
  cleanup:
    data_retention: 4320h  # 180 days

# Неправильно (deprecated)
domain:
  cleanup:
    user_retention_days: 180

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)
    // ... тестируем контроллер
}

Преимущества:

  • Не нужна реальная БД/кэш
  • Быстрые тесты
  • Легко тестировать edge cases

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

Prometheus метрики

GET /acp/system/metrics

Метрики:

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

Structured Logging

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

Alert система

internal/infrastructure/tasks/implementations/alert_dispatcher.go

Доставка через:

  • Telegram

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

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 (V1/V2):

  • Platform signature validation
  • X-Api-Key header

ACP:

  • Google OAuth
  • X-Api-Access-Token header

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.