DDD in iGaming core
The iGaming platform is a complex domain system at the intersection of finance, entertainment and compliance. DDD helps to keep complexity: highlights bounded contexts, captures ubiquitous language, protects invariants with aggregates, simplifies integrations through anti-corruption layers, and makes system behavior transparent through domain events.
1) Domain map and bounded contexts (strategic design)
Recommended decomposition:- Player/KYC Context - registration, verification, responsible game limits, KYC/AML statuses.
- Wallet/Ledger Context - balances, reservations, transactions, multicurrency, exchange rates.
- Betting Context - bets/tickets, pair/outcomes, quotes, settlement, cancellation.
- Casino/Game Round Context - sessions, rounds, backs, RTP control, betting limits.
- Bonus/Promo Context - bonus rules, wagers, acquiring bonus funds, anti-abuse.
- Risk/Fraud Context - scoring, behavioral signals, lock/timeout triggers.
- Payments Context - deposits/withdrawals, payment gateway statuses, chargeback events.
- Compliance/Reporting Context - reports to regulators, sanctions lists, audits.
- Content/Provider Integration Context - integration with game providers, catalogs, tech. statuses.
- Analytics/Read Models - projections and showcases for product readings.
2) Ubiquitous language: the core of terms
Player, Session, GameRound, Bet/Ticket,
Ledger Entry, Hold/Reserve, Settlement,
Bonus Credit / Bonus Balance, Wagering Requirement (Вейджер),
KYC Tier, Limit (deposit/session/loss), Self-Exclusion,
Provider Game, RTP Window, Risk Flag, Compliance Case.
These names are used equally in code, database, documentation, tests and interfaces.
3) Aggregates and invariants (tactical design)
3. 1 Wallet (Aggregate: `Wallet`)
Invariants:- The balance does not go into negative territory.
- Reserve + available ≤ total balance.
- The wiring is atomic and idempotent (by 'operation _ id').
- `Wallet. Reserve(amount, reason, op_id)` → `WalletReserved`
- `Wallet. Commit(op_id)` → `WalletCommitted`
- `Wallet. Rollback(op_id)` → `WalletRolledBack`
Border: Wallet doesn't know about Bet/Bonus; it serves posting and reserve transactions.
3. 2 Bet/Ticket (Aggregate: `Bet`)
Invariants:- The rate can be accepted only in the active quotation window; Amount ≤ player/session limit.
- After 'Settled', the status is' finalised '; recalculation is allowed only through compensating operations (void/recalc) with clear audit.
- `Bet. Place(player_id, amount, price, op_id)` → `BetPlaced` (требует Wallet. Reserve)
- `Bet. Settle (outcome, payout) '→' BetSettled '(requires Wallet. Commit/Release)
- `Bet. Void(reason)` → `BetVoided`
Border: Bet does not "climb" into Wallet - it calls through domain commands/orchestration.
3. 3 GameRound (Aggregate: `Round`)
Invariants:- Each spin/round has a unique 'round _ id' and an associated bet/win amount.
- The RTP window does not exceed the specified thresholds (at provider level + local rules).
- `Round. Started`, `Round. Staked`, `Round. Resulted`, `Round. Closed`.
3. 4 Bonus (Aggregate: `BonusGrant`)
Invariants:- The vager decreases only from the valid turnover, the bonus write-offs do not go into debit.
- It is not possible to write off the bonus and real funds at the same time not according to the priority rule.
- `BonusGranted`, `BonusWagered`, `BonusExpired`, `BonusConverted`.
4) Orchestrations, sagas and coherence
Synchronous (CP): acceptance of bet and reserve of funds - one way: 'Bet. Place` → `Wallet. Reserve '(via domain team/orchestrator with deadline).
Asynchronous (EC): rate calculation, bonus accrual, analytics - through events + outbox.
TCC variant: 'TryReserve' (hold), 'Confirm' (commit), 'Cancel' (rollback) for monetary effects.
Idempotence: all commands carry 'operation _ id', consumers - 'inbox'.
5) Anti-corruption layers (ACLs) and integrations
Provider ACL: translation of provider events' SpinResult ',' BonusWin'to internal'Round. Resulted`, `BonusWagered`. Schemas and versions are inside the ACL.
PSP ACL: normalization of payment statuses, idempotency by 'psp _ tx _ id', transfer to 'LedgerEntry'.
Compliance ACL: integration with sanctions lists/RAP - in an external context; only normalized'ScreeningUpdated 'get inside the domain.
Rule: external dictionaries/formats do not "leak" into the kernel.
6) Projections and Read Models
Player Profile Read Model: KYC statuses, limits, active bonuses, fresh transactions.
Balances Read Model: Fast Reads for UI/Marketing; source - 'Wallet' events.
Bet History Read Model: Pagination by Date/Game; source is' BetPlaced/Settled '.
Compliance Reports - Materialized Views by Tenant/Region.
All projections are idempotent upserts with versioning and'as _ of/freshness'.
7) Multi-tenant and multi-region
All key entities carry 'tenant _ id' and (if necessary) 'region'.
Data boundaries: player, wallet, bets - "home" region; cross-regional aggregates/reports only.
Fairness/quotas: limits on teams/sec and redrives on tenants.
Residency/compliance: personal data and transactions do not leave the region.
8) Consistency Selection (PACELC) by Context
Wallet/Ledger - Strong/CP: linearizable transactions, quorum of records.
Bet acceptance - synchronous confirmation (CP) + fast Read Models for UI.
Settlement/Bonus/Analytics - EC with deterministic merge/compensation.
KYC/Compliance - can be EC for statuses, but "blocking" rules are applied synchronously.
9) Domain Events: Contracts and Version
Minimum set of fields: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"
}
Rules:
- Back/forward-compat schemes; evolution via 'schema _ version'.
- 'outbox'in a transaction with domain changes; publication by butches with backoff.
10) Example of "Bet with Bonus" flow (word sequence)
1. `Bet. Place '(team) → checking player limits and →' Wallet bonus rules. Reserve(real+bonus_equiv, op_id)`
2. 'BetPlaced' → Read Model updates' Open Wagers'
3. The provider publishes the result → the → 'Round ACL. Resulted`
4. Orchestrator calculates: 'Bet. Settle(outcome,payout)` → `Wallet. Commit (op_id) 'and, if won,' BonusWagered '→ a possible conversion of the bonus into real ones.
5. 'BetSettled' → projections of history and balance sheets, reporting.
11) Invariants and testing policy
Key invariants:- The sum of all 'LedgerEntry' in the wallet is equal to the balance; no negative residuals.
- You cannot accept a bet with an active self-exclusion/frozen KYC status.
- The wager can only decrease and not swing "in minus."
- Settlement does not change the status of the already finalized rate - only through 'Void/Recalc' + offsetting transaction.
- Property-based tests of wallet invariants and bets.
- Contours of chaos: provider delays, PSP failures, outbox/DLQ redrives.
- Schema control: event migrations, backfill projections.
12) Telemetry and auditing
Metrics: p95/p99 on PlaceBet/Reserve/Commit, share of failures by limits/ACC, DLQ rate, redrive success, lag projections.
Tracing: spans "komanda→agregat→outbox→konsyumer→proyektsiya," tags' tenant _ id ',' operation _ id ',' saga _ id '.
Audit: an unchanging log of domain activities comparable to regulatory requirements.
13) Storage scheme (simplified)
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)
Versioning on aggregates ('version') will protect against lost update during competitive recording.
14) Example Command API (pseudo)
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":"..." }
All commands are with 'operation _ id' for idempotency, responses are with 'as _ of '/' version'.
15) Safety and compliance
RLS/ACL: all requests - in the context of'tenant _ id', access by role.
PII-minimization: separation of domain events from personal data; masking in DLQ/logs.
Regulatory reports: projections with unchanging hash signatures in time windows.
16) Typical errors
Strong connectivity between contexts (Wallet knows Bet/Bonus directly).
Dual-write to different contexts without sagas/outbox → mismatch of balances and statuses.
Lack of command and consumer idempotency → duplicate transactions/calculations.
The flow of provider contracts into the domain model (it is more difficult to migrate).
One "giant" aggregate (Player includes all) lock →, low throughput.
There are no obvious invariants - they cannot be checked and monitored.
17) Quick recipes
Start: fix Ubiquitous Language and context boundaries; document invariants.
Money: Wallet/Ledger - CP, quorum entries, TCC for external effects.
Bets: synchronous reception + asynchronous calculation, all through events and outbox; idempotency is everywhere.
Bonuses: separate unit with clear write-offs priority and vager.
Integrations: always via ACL + schemes/versions; no "raw" payloads in the core.
Readings: projections/displays on product needs; SLA freshness + 'as _ of'.
Operating: metrics of invariants, DLQ/redrave playbooks, rebuild showcases.
18) Pre-sale checklist
- Bounded contexts and their contracts (commands/events) are defined.
- Aggregates have explicit invariants, versioning, and idempotent commands.
- Monetary transactions - through TCC/strict transactionality; audit enabled.
- Integrations - via ACLs with schema versioning and evolution tests.
- Implemented outbox/inbox, DLQ and secure redraw.
- Projections implement SLA freshness, there are lag/staleness metrics.
- Multi-tenant quotas/limits and data residency are met.
- Observability: tracing "komanda→sobytiye→proyektsiya," alerts by invariants.
- Documentation: domain language, context diagrams, incident playbooks.
Conclusion
DDD in the iGaming core is a discipline of separation of complexity: clear context boundaries, aggregates with invariants, events as a source of truth, ACLs for external integrations, and informed consistency choices. This approach makes the platform scalable, reliable and compliant with regulations, speeds up the development of features and reduces operational risks - even with the rapid growth of traffic, geographies and product lines.