GH GambleHub

DDD в iGaming-ядре

iGaming-платформа — это сложная доменная система на стыке финансов, развлечений и комплаенса. DDD помогает удерживать сложность: выделяет bounded contexts, фиксирует ubiquitous language, защищает инварианты агрегатами, упрощает интеграции через антикоррупционные слои и делает поведение системы прозрачным благодаря доменным событиям.

1) Карта доменов и bounded contexts (стратегический дизайн)

Рекомендуемая декомпозиция:
  • Player/KYC Context — регистрация, верификация, лимиты ответственной игры, статусы KYC/AML.
  • Wallet/Ledger Context — балансы, резервирования, проводки, мультивалютность, курсы.
  • Betting Context — ставки/тикеты, пара/исходы, котировки, расчет (settlement), отмена.
  • Casino/Game Round Context — сессии, раунды, спины, RTP-контроль, лимиты ставок.
  • Bonus/Promo Context — правила бонусов, вагер, эквайринги бонусных средств, анти-абьюз.
  • Risk/Fraud Context — скоринг, поведенческие сигналы, триггеры блокировок/тайм-аутов.
  • Payments Context — депозиты/выводы, статусы платежных шлюзов, chargeback-события.
  • Compliance/Reporting Context — отчеты регуляторам, санкционные списки, аудит.
  • Content/Provider Integration Context — интеграция с провайдерами игр, каталоги, тех. статусы.
  • Analytics/Read Models — проекции и витрины под продуктовые чтения.
💡 Межконтекстные связи — через доменные события и асинхронные команды; синхронные вызовы — только там, где это действительно контракт домена и нужна строгая согласованность.

2) Ubiquitous language: ядро терминов

Player (Игрок), Session (Сессия), GameRound (Раунд), Bet/Ticket (Ставка),

Ledger Entry (Проводка), Hold/Reserve (Резерв), Settlement (Расчет),

Bonus Credit / Bonus Balance, Wagering Requirement (Вейджер),

KYC Tier, Limit (депозит/сессия/потери), Self-Exclusion,

Provider Game, RTP Window, Risk Flag, Compliance Case.

Эти названия одинаково употребляются в коде, БД, документации, тестах и интерфейсах.

3) Агрегаты и инварианты (тактический дизайн)

3.1 Wallet (Aggregate: `Wallet`)

Инварианты:
  • Баланс не уходит в минус.
  • Резерв + доступный ≤ общий баланс.
  • Проводка атомарна и идемпотентна (по `operation_id`).
Команды/события:
  • `Wallet.Reserve(amount, reason, op_id)` → `WalletReserved`
  • `Wallet.Commit(op_id)` → `WalletCommitted`
  • `Wallet.Rollback(op_id)` → `WalletRolledBack`

Граница: Wallet не знает про Bet/Bonus; он обслуживает операции проводок и резервов.

3.2 Bet/Ticket (Aggregate: `Bet`)

Инварианты:
  • Ставка может быть принята только в активном окне котировок; сумма ≤ лимита игрока/сессии.
  • После `Settled` статус «финализирован»; повторный расчет допускается только через компенсирующие операции (void/recalc) с четким аудитом.
Команды/события:
  • `Bet.Place(player_id, amount, price, op_id)` → `BetPlaced` (требует Wallet.Reserve)
  • `Bet.Settle(outcome, payout)` → `BetSettled` (требует Wallet.Commit/Release)
  • `Bet.Void(reason)` → `BetVoided`

Граница: Bet не «лезет» в Wallet — обращается через доменные команды/оркестрацию.

3.3 GameRound (Aggregate: `Round`)

Инварианты:
  • Каждый спин/раунд имеет уникальный `round_id` и связанную сумму ставки/выигрыша.
  • RTP-окно не превышает заданные пороги (на уровне провайдера + локальные правила).
События:
  • `Round.Started`, `Round.Staked`, `Round.Resulted`, `Round.Closed`.

3.4 Bonus (Aggregate: `BonusGrant`)

Инварианты:
  • Вейджер уменьшается только от валидного оборота, списания бонуса не уходят в дебет.
  • Невозможно одновременно списать бонус и реальные средства не по правилу приоритета.
События:
  • `BonusGranted`, `BonusWagered`, `BonusExpired`, `BonusConverted`.

4) Оркестрации, саги и согласованность

Синхронно (CP): прием ставки и резерв средств — единый путь: `Bet.Place` → `Wallet.Reserve` (через доменную команду/оркестратор с дедлайном).
Асинхронно (EC): расчет ставки, начисление бонусов, аналитика — через события + outbox.
TCC-вариант: `TryReserve` (hold), `Confirm` (commit), `Cancel` (rollback) для денежных эффектов.
Идемпотентность: все команды несут `operation_id`, консьюмеры — `inbox`.

5) Антикоррупционные слои (ACL) и интеграции

Provider ACL: трансляция провайдерских событий `SpinResult`, `BonusWin` в внутренние `Round.Resulted`, `BonusWagered`. Схемы и версии — внутри ACL.
PSP ACL: нормализация статусов платежей, идемпотентность по `psp_tx_id`, перевод в `LedgerEntry`.
Compliance ACL: интеграции со списками санкций/PEP — во внешнем контексте; внутрь домена попадают только нормализованные `ScreeningUpdated`.

Правило: внешние словари/форматы не «просачиваются» внутрь ядра.

6) Проекции и Read Models

Player Profile Read Model: статусы KYC, лимиты, активные бонусы, свежие транзакции.
Balances Read Model: быстрые чтения для UI/маркетинга; источник — `Wallet` события.
Bet History Read Model: пагинация по датам/играм; источник — `BetPlaced/Settled`.
Compliance Reports: материализованные представления по тенанту/региону.

Все проекции — идемпотентные upsert’ы с версионированием и `as_of/freshness`.

7) Мульти-тенант и мульти-регион

Все ключевые сущности несут `tenant_id` и (при необходимости) `region`.
Границы данных: игрок, кошелек, ставки — «домашний» регион; кросс-региональные только агрегаты/отчеты.
Fairness/квоты: лимиты на команды/сек и редрайвы по тенантам.
Residency/комплаенс: персональные данные и проводки не покидают регион.

8) Выбор согласованности (PACELC) по контекстам

Wallet/Ledger — Strong/CP: линейризуемые проводки, кворум записей.
Bet acceptance — синхронное подтверждение (CP) + быстрые Read Models для UI.
Settlement/Bonus/Analytics — EC с детерминированным merge/компенсациями.
KYC/Compliance — может быть EC для статусов, но «блокирующие» правила применяются синхронно.

9) Доменные события: контракты и версия

Минимальный набор полей:
json
{
"event_id": "uuid",
"event_type": "BetPlaced",
"occurred_at": "timestamp",
"tenant_id": "T123",
"aggregate_id": "BET-...-UUID",
"version": 7,
"payload": { "...domain fields..." },
"schema_version": "v3"
}
Правила:
  • Back/forward-compat схем; эволюция через `schema_version`.
  • `outbox` в транзакции с доменными изменениями; публикация батчами с backoff.

10) Пример потока «Ставка с бонусом» (словесная последовательность)

1. `Bet.Place` (команда) → проверка лимитов игрока и правил бонуса → `Wallet.Reserve(real+bonus_equiv, op_id)`

2. `BetPlaced` (событие) → Read Model обновляет «Открытые ставки»

3. Провайдер публикует результат → ACL → `Round.Resulted`

4. Оркестратор рассчитывает: `Bet.Settle(outcome,payout)` → `Wallet.Commit(op_id)` и, при выигрыше, `BonusWagered` → возможная конверсия бонуса в реальные.
5. `BetSettled` → проекции истории и балансов, отчетность.

11) Инварианты и политика тестирования

Ключевые инварианты:
  • Сумма всех `LedgerEntry` по кошельку равна балансу; нет отрицательных остатков.
  • Нельзя принять ставку при активном self-exclusion/замороженном KYC-статусе.
  • Вейджер может только уменьшаться и не качаться «в минус».
  • Settlement не меняет статус уже финализированной ставки — только через `Void/Recalc` + компенсирующая проводка.
Тестирование:
  • Property-based тесты инвариантов кошелька и ставок.
  • Контуры хаоса: задержки провайдера, отказы PSP, редрайвы outbox/DLQ.
  • Контроль схем: миграции событий, backfill проекций.

12) Телеметрия и аудит

Метрики: p95/p99 на PlaceBet/Reserve/Commit, доля отказов по лимитам/KYC, DLQ rate, redrive success, lag проекций.
Трейсинг: спаны «команда→агрегат→outbox→консьюмер→проекция», теги `tenant_id`, `operation_id`, `saga_id`.
Аудит: неизменяемый журнал доменных действий, сопоставимый с регуляторными требованиями.

13) Схема хранения (упрощенно)

Wallet:

wallet(id, tenant_id, currency, balance, reserved, version)
ledger(id, wallet_id, amount, type, operation_id, occurred_at)
holds(id, wallet_id, amount, operation_id, expires_at, status)
Bet:

bet(id, tenant_id, player_id, amount, price, status, placed_at, settled_at, operation_id)
Bonus:

bonus_grant(id, tenant_id, player_id, amount, wager_left, status, expires_at)

Версионирование на агрегатах (`version`) защитит от lost update при конкурентной записи.

14) Пример API команд (псевдо)

http
POST /bets. place
{
"tenant_id":"T1",
"player_id":"P42",
"amount":"10. 00",
"price":"2. 1",
"operation_id":"op-uuid",
"context":{"game_id":"g777","channel":"web"}
}
→ 202 Accepted + BetPlaced

POST /wallets. reserve
{ "wallet_id":"W1", "amount":"10. 00", "operation_id":"op-uuid", "reason":"bet" }
→ 200 { "reserved_balance":"..." }

Все команды — с `operation_id` для идемпотентности, ответы — с `as_of`/`version`.

15) Безопасность и соответствие

RLS/ACL: все запросы — в контексте `tenant_id`, доступ по ролям.
PII-минимизация: отделение доменных событий от персональных данных; маскировка в DLQ/логах.
Регуляторные отчеты: проекции с неизменяемыми хэш-подписями по окнам времени.

16) Типичные ошибки

Сильная связность между контекстами (Wallet напрямую знает Bet/Bonus).
Dual-write в разные контексты без саг/outbox → рассогласование балансов и статусов.
Отсутствие идемпотентности команд и консьюмеров → дубли проводок/расчетов.
Протекание провайдерских контрактов в доменную модель (сложнее мигрировать).
Один «гигантский» агрегат (Player включает все) → блокировки, низкий throughput.
Нет явных инвариантов — их невозможно проверить и мониторить.

17) Быстрые рецепты

Старт: зафиксируйте Ubiquitous Language и контекстные границы; документируйте инварианты.
Деньги: Wallet/Ledger — CP, кворумные записи, TCC для внешних эффектов.
Ставки: синхронный прием + асинхронный расчет, все через события и outbox; идемпотентность везде.
Бонусы: отдельный агрегат с четким приоритетом списаний и вейджером.
Интеграции: всегда через ACL + схемы/версии; никаких «сырьевых» payload’ов в ядре.
Чтения: проекции/витрины на потребности продукта; SLA свежести + `as_of`.
Оперирование: метрики инвариантов, DLQ/редрайв плейбуки, rebuild витрин.

18) Чек-лист перед продом

  • Определены bounded contexts и их контракты (команды/события).
  • Агрегаты имеют явные инварианты, версионирование и идемпотентные команды.
  • Денежные операции — через TCC/строгую транзакционность; аудит включен.
  • Интеграции — через ACL с версионированием схем и тестами эволюции.
  • Внедрены outbox/inbox, DLQ и безопасный редрайв.
  • Проекции реализуют SLA свежести, есть метрики lag/staleness.
  • Мульти-тенантные квоты/лимиты и data residency соблюдены.
  • Наблюдаемость: трейсинг «команда→событие→проекция», алерты по инвариантам.
  • Документация: язык домена, диаграммы контекстов, плейбуки инцидентов.

Заключение

DDD в iGaming-ядре — это дисциплина разделения сложности: четкие границы контекстов, агрегаты с инвариантами, события как источник правды, ACL для внешних интеграций и осознанный выбор согласованности. Такой подход делает платформу масштабируемой, надежной и соответствующей регуляциям, ускоряет разработку фич и снижает операционные риски — даже при быстром росте трафика, географий и продуктовой линейки.

Contact

Свяжитесь с нами

Обращайтесь по любым вопросам или за поддержкой.Мы всегда готовы помочь!

Telegram
@Gamble_GC
Начать интеграцию

Email — обязателен. Telegram или WhatsApp — по желанию.

Ваше имя необязательно
Email необязательно
Тема необязательно
Сообщение необязательно
Telegram необязательно
@
Если укажете Telegram — мы ответим и там, в дополнение к Email.
WhatsApp необязательно
Формат: +код страны и номер (например, +380XXXXXXXXX).

Нажимая кнопку, вы соглашаетесь на обработку данных.