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: інтеграції зі списками санкцій/РЕР - у зовнішньому контексті; всередину домену потрапляють тільки нормалізовані «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, частка відмов по лімітах/КУС, DLQ rate, redrive success, lag проекцій.
Трейсинг: спани «komanda→agregat→outbox→konsyumer→proyektsiya», теги'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 і їх контракти (команди/події).
- Агрегати мають явні інваріанти, версіонування та ідемпотентні команди.
- Грошові операції - через ТСС/сувору транзакційність; аудит включено.
- Інтеграції - через ACL з версіонуванням схем і тестами еволюції.
- Впроваджені outbox/inbox, DLQ і безпечний редрайв.
- Проекції реалізують SLA свіжості, є метрики lag/staleness.
- Мульти-тенантні квоти/ліміти і data residency дотримані.
- Спостережуваність: трейсинг «komanda→sobytiye→proyektsiya», алерти за інваріантами.
- Документація: мова домену, діаграми контекстів, плейбуки інцидентів.
Висновок
DDD в iGaming-ядрі - це дисципліна поділу складності: чіткі межі контекстів, агрегати з інваріантами, події як джерело правди, ACL для зовнішніх інтеграцій і усвідомлений вибір узгодженості. Такий підхід робить платформу масштабованою, надійною і відповідною регуляціям, прискорює розробку фіч і знижує операційні ризики - навіть при швидкому зростанні трафіку, географій і продуктової лінійки.