Alexei Rojas Quiroga
← All projects

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

Sports Wagering Platform

Event-driven bet intake, validation and settlement pipeline on Kafka and .NET

Status
In production
Role
Senior backend engineer: service and pipeline design, Kafka handlers, live-limits engine, live-bet validation and hand-off, resilience, CI/CD and Kubernetes packaging.

Architecture

  • Kafka-decoupled intake and validation
  • Shared limits engine
  • Per-player locks, idempotent hand-off to the live API

The wager is accepted and queued. Limits and the player's history are checked, then the accepted wager is handed to the live engine's bet API, which inserts it into the live DB.

Architecture · 7 nodes · 2 flows
WHAT I BUILTWeb clientBetting front-endPlaces wagers, receiveslive stateASP.NET CoreBet intake APIAccepts wagers, pre-checks limits.NET libraryLimits enginePure, deterministicbetting rulesKafka + AvroValidation streamWager requests.NET consumerBet validationserviceAge, odds, limits,historySQL ServerLive wageringdatabaseLive wagers and historyfor capsASP.NET CoreLive engine · betAPIBelongs to the livebetting engine; insertsthe wager1234567
  • Service / compute
  • Data store
  • Queue / messaging
  • Client
  • 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

Backend microservice suite for an online sports betting operator. It accepts pregame and live wagers, validates them against player limits and live odds, persists them into the bet ledger and pushes every state change back to the front-end in real time. Live wagers add history-aware limits checked under per-player locks, then an idempotent hand-off to the live engine's bet API.

The problem

A monolithic, synchronous bet-insertion path could not absorb peak event traffic, gave players no feedback while a bet was being validated, and tied limit rules to a legacy stored-procedure model that was hard to test or evolve. The operator needed to decouple intake from validation and persistence, keep strict ordering per player, and introduce richer live-betting limit rules without breaking the legacy ledger.

What I built

  • Designed a Kafka-decoupled pipeline (intake, validation, insertion) with a dedicated notification stream, so every wager moves through explicit states and the player always sees progress.
  • Extracted a deterministic, infrastructure-free limits engine as a versioned NuGet package (static limits plus history-aware per-game caps and surpass rules) shared by the API, the validation service and a new Blazor client.
  • Made live-bet validation concurrency-safe using per-player application locks and bounded semaphores for catalog lookups, rejecting cleanly when upstream limit data is missing or inconsistent.
  • Added idempotency and age thresholds (stale wagers are rejected at both validation and persistence), plus stable transaction identifiers propagated through structured logs and notifications.
  • Wrapped the legacy bet-insertion library behind an HTTP legacy ledger API so the new pipeline could ship incrementally while the legacy ledger remained the system of record.
  • Shipped multi-environment CI/CD: unit and end-to-end test stages, container builds, Helm-based rollouts, and parallel IIS deployment for legacy-hosted components.

Key decisions and why

01

Kafka as the backbone between intake, validation and persistence

Decoupling lets the API acknowledge instantly, absorbs event-day spikes, and allows each stage to scale and fail independently. Messages are keyed by player so ordering is preserved per account, with manual offset commits and bounded retries.

02

Limits engine as a pure library, not a service

Keeping the rules free of HTTP, database and Kafka dependencies makes them fully unit-testable and reusable on the server and in the browser. Callers supply a history snapshot; the engine only decides. Versions are pinned explicitly so contract changes are revalidated together.

03

Authoritative second validation under a per-player lock

A live wager is re-validated against the freshest limit snapshot and again under a per-player lock, so concurrent bets cannot jointly breach per-game caps. The public pre-check endpoint is explicitly preliminary.

04

Dedicated notification stream feeding SignalR

Every service emits wager state changes to one topic; a single hub fans them out to clients. Services never talk to the front-end directly, which keeps them stateless and horizontally scalable.

05

Strangler-style wrapper around the legacy insertion path

Re-implementing years of ledger rules was riskier than wrapping them. The legacy library is hosted behind an HTTP API, giving the new pipeline a stable contract while the rules are migrated feature by feature.

06

Resilient HTTP clients with explicit timeouts

All upstream calls (odds, player profile, legacy API) use typed clients with retry, timeout and circuit-breaker policies. Failures translate into explicit business error keys instead of silent drops.

Tech stack

Languages
C#
Backend
.NET 10ASP.NET CoreSignalRPolly / Http.Resilience
Messaging
KafkaAvro / Schema Registry
Data
SQL ServerDapper
DevOps
OpenTelemetrySerilogDockerHelm / KubernetesGitLab CINuGet (private feed)
Testing
xUnit