Alexei Rojas Quiroga
← All projects

Corporate project · Presented company-agnostic: architecture and decisions only.

Live Betting Real-Time Engine

Multi-tenant grading, limits and settlement workers for live sports betting

Status
In production
Role
Senior backend engineer: architecture, grading and accounting workers, Kafka resilience, SQL grading procedures, reporting workers, Blazor admin UI and CI/CD.

Architecture

  • Database-queue grading pipeline
  • Serializable settlement
  • 50 wagers per Kafka message
  • Retry tiers and dead-letter
  • Idempotent ledger writes

A finished game is graded, wagers are settled, batched per tenant and applied to the ledger.

Architecture · 10 nodes · 2 flows
WHAT I BUILT.NET worker se…Game grading workerRuns the gradingprocedure per gameSQL ServerWagering & gradingdatabaseWagers, queues,results, facts.NET worker se…Wager gradingworkerSettles accountingtransactionally.NET worker se…Outbox publisherBatches 50 gradedwagers per messageKafkaPer-tenant gradetopicsGraded results pertenant.NET worker se…Grade sync workerApplies grading,retries, dead-letterASP.NET CoreBet integration APIIdempotent insert andgradingSQL ServerBet ledger databaseWagers, balances,accountingKafkaRetry & dead-lettertopicsBackoff, then dead-letterChat webhookChat alertingDead-letter and oddsalerts12345678
  • Service / compute
  • Data store
  • Queue / messaging
  • External system
  • Synchronous
  • Async / loop
Scroll sideways to see the full diagram

How it flows, step by step

Click a step to jump to it. Click a component for details.

What it does

Multi-tenant back-office and settlement engine for an online sports betting operator's live product. It grades live straight and parlay wagers when games finish, posts accounting to per-tenant Kafka streams, keeps odds integrity audits, and serves limits configuration, reporting and a Blazor administration UI.

The problem

Live betting generated a high volume of short-lived wagers that had to be graded exactly like the legacy ledger, without double-settling, across several tenants and with strict financial correctness. The legacy approach was a single, tightly coupled grading flow with no retry or dead-letter handling and little visibility into discrepancies.

What I built

  • Built a database-queue grading pipeline: a game-finished trigger runs a grading procedure, enqueues wagers, and a grading worker settles accounting inside SERIALIZABLE transactions to protect player balances.
  • Added an outbox-style worker that batches graded wagers (50 per message) into per-tenant Kafka topics, cutting broker round trips by roughly 98 percent versus one message per wager.
  • Implemented a three-tier Kafka consumption model (main topic, exponential-backoff retry topic, dead-letter topic with chat alerting) behind a generic message-handler abstraction.
  • Guaranteed idempotent grading using unique bet-group tracking and optimistic concurrency, and validated results against the legacy system with a dedicated comparison CLI that surfaced rounding variances in reduced parlays.
  • Delivered a family of scheduled workers: score ingestion with dead-lettering, timeline derivation and reconciliation, weekly top-player and hold-percentage fact tables, and an odds line-integrity auditor that compares each accepted bet to archived market data.
  • Delivered a modular Blazor admin UI (ten component libraries) with a tenant-configurable theme and Redis-backed caching, plus reporting endpoints that answer in under two seconds from precomputed facts.

Key decisions and why

01

Database queue for grading, Kafka for fan-out

Grading must be atomic with balance updates, so the queue lives in the same transactional store as the wagers. Kafka is used only after settlement to distribute results to tenants, where at-least-once delivery with idempotent consumers is acceptable.

02

Retry topic and dead-letter instead of in-place retry loops

Failing messages move off the hot path so one poison message cannot block a partition. Backoff is applied on the retry topic and permanent failures are surfaced to operators with context.

03

Batching in the outbox worker

Single-wager messages saturated the broker at peak. Batching 50 wagers per message per tenant preserved ordering by tenant while multiplying throughput.

04

Workers never crash the host on bad data

Message handlers return an explicit disposition (commit, retry, dead-letter). Permanent data errors are dead-lettered and committed, infrastructure errors retry, and a health endpoint exposes last-successful-message time so monitoring decides about restarts.

05

Precomputed weekly facts for reporting

Aggregating settled wagers on demand was too slow. Scheduled workers recompute a rolling window to absorb late gradings, so the report API serves indexed fact tables in seconds.

06

Clean Architecture with Result types

Domain, application, infrastructure and API layers register through extension methods, and business operations return Result values instead of exceptions, keeping error mapping explicit and unit tests simple.

Tech stack

Languages
C#
Backend
.NET 10ASP.NET Core Minimal APIsPollyFluentValidation
Data
Entity Framework CoreDapperSQL ServerRedis
Messaging
Kafka
Frontend
Blazor WebAssemblyMudBlazor
Testing
xUnit
DevOps
Windows Services / IISGitLab CI
Other
Slack webhooks