Fantasy football off AWS: 31 Lambdas to one Go service on GKE by Alikhan UrumovFantasy football off AWS: 31 Lambdas to one Go service on GKE by Alikhan Urumov
Fantasy football off AWS: 31 Lambdas to one Go service on GKE
Fantasy football for Funcy Network: squads of 15, a captain and vice-captain, formations, transfers and weekly rounds across eight competitions, with live scores.
The original backend was .NET 8 on AWS — 31 Lambda functions, 19 SQS queues, 17 queue workers, RDS and Cognito — with no tests at all. I rewrote it as one Go service on PostgreSQL, running on GKE on the network's shared identity and wallet. The game was cut over and the AWS stack decommissioned. The service now carries 55 migrations and 1,737 test functions.
The constraint
With no tests, the behaviour existed only in the code. Every wave of the port started by reading the original and pinning what it actually did in characterization tests — its bugs included, each one listed and decided rather than copied in silence. API paths were kept verbatim.
What the port fixed on the way
Durable events leave only through a transactional outbox. The original published straight to the queue, so a bus outage could silently drop points
Duplicate guards from day one. The original gained its unique indexes only after an at-least-once queue credited players twice
Budgets and prices are integers in hundredths. No floating point anywhere near money
One internal player id from the shared identity provider replaces the per-game Cognito lookup
Nine gamification tables that nothing in the new service used were dropped; levels, achievements and notifications live in the network's shared services
Settlement: exactly one event per round
When a round closes and every match is processed, settlement scores each squad — captain doubled, automatic substitutions, the vice-captain stepping in — then ranks, leaderboards, a round summary and post-round price changes, and enqueues exactly one round-settled event in the same transaction
The drainer publishes with exponential backoff; after ten failures an event moves to a dead-letter topic instead of disappearing. Replicas never compete for the same rows
Closing a season re-announces its final round as a redelivery, so the rewards engine grants the season title once without counting the round twice
Running it
Two replicas start at once, so every migration runs under a transaction-scoped advisory lock; a session-scoped one would leak behind PgBouncer in transaction mode
Live scores are pulled under a football-data API budget shared with another game through Redis
A new season with no player statistics yet imports from club rosters, and the price model refuses to rank players it has no signal for instead of inventing an order
Like this project
Posted Sep 28, 2026
A .NET 8 backend on AWS with 31 Lambdas, 19 SQS queues and no tests, rewritten in Go on GKE with behaviour pinned by tests. The AWS stack is gone.