2026 · Lead Architect & Software Engineer

Arcadia

A Steam-like digital game platform built as 14 event-driven microservices — publishing, wallet and gift cards, an item marketplace with a real order-matching engine, community, and a hybrid recommendation engine — deployed to Kubernetes with Prometheus, Grafana, and Loki observability.

Next.jsPythonGoPostgreSQLKafkaRedisKubernetesPrometheusGrafanaLoki

Arcadia screenshots

1 / 26

Overview

Arcadia is a digital game distribution platform — a Steam-like store where developers publish games, players buy and gift them from a wallet, an item marketplace lets players trade in-game items against a live order book, and a community layer wraps it all in reviews, posts, and festivals. It began as the architecture project for a graduate microservices course, but the brief was never "make it work" — it was "make it correct under failure," which is a different and much harder assignment. It shipped as 14 independently deployed services across a real Kubernetes cluster, with the observability stack needed to actually operate it.

Bounded contexts, enforced, not assumed

Fourteen services — Auth & Profile, Catalog, Order, Wallet, Payment, Marketplace, Review, Community, Search, Recommendation, Media, Notification, Festival, and the API Gateway — each own their domain outright: their own database, their own deploy pipeline, their own on-call story. Database-per- service is usually a diagram; here it is a guarantee enforced by Postgres grants, not convention. CONNECT is revoked from PUBLIC on every database, so the wallet service's role physically cannot open a connection to the payment service's tables — exactly as true as if they ran on different hosts, because a design that only happens to keep services from touching each other's data is a design that will eventually be violated by someone in a hurry.

Money has to be exactly right, not usually right

A store that sells things needs its ledger to never drift, and distributed transactions are exactly where that goes wrong first. A purchase is a saga: order reserves the game, wallet holds the funds, payment settles, and if any step fails the prior steps unwind through compensating transactions rather than leaving the system in a half-completed state. Every write that must both land in a database and announce itself on Kafka goes through the Outbox pattern, so a crash between "committed" and "published" is impossible by construction rather than merely unlikely. Messages that a consumer cannot process do not vanish — every consumed topic has a dead-letter companion, and the two command topics (wallet-commands, catalog-commands) dead-letter on so much as an unrecognized message, because an addressed command a service doesn't understand is a contract violation someone needs to see, not noise to swallow. The reward split on every sale — 70% to the developer, 30% to the platform — runs through the same guarantee, so "the money is right" is not a property you check afterward with a script; it is a property the design cannot violate.

A marketplace with a real matching engine

Beyond buying published games, players trade in-game items directly: list an item for sale or place a buy order, and the marketplace service runs actual price-time-priority matching against a live order book — the same mechanism that prices a stock exchange, scaled down to game items and required to clear outstanding orders in seconds. It is a genuinely different problem from the CRUD-shaped features around it, and it is treated as one: its own bounded context, its own periodic matching job, its own failure mode that cannot be allowed to touch the wallet ledger except through the same saga discipline as everything else that moves money.

Recommendations that combine two different signals

The recommendation service is a hybrid engine, not a single model bolted on for a demo. It consumes PurchaseCompleted, ReviewPosted, and GamePublished events to build two complementary views: a content-based similarity model over game genre and tag embeddings, stored as vector columns in a pgvector-enabled Postgres (the platform runs pgvector/pgvector:pg16 specifically because the stock image cannot open this extension — a migration that fails silently there leaves the service reporting healthy while every query against the new column errors), and collaborative filtering over what similar players bought. Combining the two is what makes "games like this one" and "players like you bought this" both answerable from the same service instead of needing two.

Making event-driven correctness legible

Fourteen services sharing a Kafka bus is only as maintainable as the map of who talks to whom, so that map is written down and treated as a design artifact, not tribal knowledge: the notification service deliberately does not consume wallet-events (a balance change is not news to the person who just spent the money — what's news is the order it paid for), and does not read review or community events either, because a reported comment is Support's concern and Support already sees it in the queue inside the service that owns it. Getting an event's consumers right is as much a design decision as getting its schema right, and it's the kind of decision that is invisible until it's wrong.

Running it for real

The platform deploys to a k3s cluster behind Traefik with cert-manager, each service on its own autoscaler (the gateway floors at 3 replicas, domain services at 1, both scaling to CPU), and an observability stack where the dashboards are generated code: one Grafana dashboard per service plus an overview, produced by a script that runs in CI so a dashboard cannot silently drift from the metrics it queries. Logs correlate across all 14 services by a single request id, so tracing one purchase across order, wallet, payment, and notification is one Loki query, not four terminal tabs. A 123-check end-to-end suite exercises the real workflows — a game published, bought, refunded, and gifted; a marketplace trade matched under a genuine race; every dead-letter queue confirmed empty; every ledger balance confirmed equal to the sum of its own history — because five services once passed 389 green unit tests and still produced nine bugs in the first hour the stack ran for real, and every one of those nine was about configuration and topology, the exact class of bug unit tests cannot see.