A demo of a scalable, event-driven architecture (Kafka + Redis + microservices in Rust/Java) originally built for a collaborative MIDI-puzzle game (“Guess My Groove”). The game proved unfun and hard to extend given the encoding implementation, so the focus here is on the architecture. (If I did it again today, I’d swap in SpaceTimeDB.)
Note on R2 You don’t actually need Cloudflare R2—there’s no "disable R2" flag, so you’ll have to run a localstack (or any S3-compatible) service to satisfy those settings. If I’d spent more time I would have just written snapshots to local files, but this lets you load huge
.midfiles.
- Load a MIDI file.
- Encode each MIDI event (Note On/Off, Tempo, etc.) as a digit sequence.
- Show each sequence as a “row” players can increment/decrement.
- When a row’s digits match the hidden sequence, reveal that event.
- When a group of rows completes, play back that snippet.
- Goal: reconstruct and hear the entire piece collaboratively.
+--------------+ +-----------------+
| gmg_frontend |----->| gmg_backend |----->---+
| (WASM/Dioxus)| (WS) | (Rust/Axum) | |
+--------------+ +-----------------+ | (Produce/Consume Kafka Events)
| e.g., Init, Updates, Broadcasts
v
+-------------------------+ +-----------------+ +-----------------+
| gmg_stream |<------------------| Kafka (Redpanda)|------------------>| gmg_processor |----->---+
| (Java/Kafka Streams) | | (Event Bus) | | (Rust) | |
| | +-----------------+ | | | (Read/Write State)
| *Consumes* row-state | ^ ^ | *Consumes* Init,| |
| *Produces* completions | | | | Updates, Complete| |
+-------------------------+ |---------+-----------------------| *Produces* Snap- | |
(Produce/Consume Kafka Events) (Produce/Consume Kafka Events) | shots, Updates | |
+-----------------+ |
|
v
+-------+-------+
| Redis |
| (Game State) |
+---------------+
- gmg_core (Rust lib): shared types, MIDI codec, Redis/Kafka helpers
- gmg_backend (Rust/Axum): WebSocket → Kafka relay
- gmg_processor (Rust): game logic, MIDI I/O, Redis state
- gmg_frontend (Rust/Dioxus/WASM): UI + audio playback (
rustysynth) - gmg_stream (Java/Kafka Streams): section-completion logic
- Event-driven (Kafka)
- Microservices + loose coupling
- CQRS‐style separation
- Externalized state in Redis
- Real-time WebSocket updates
- Stateful stream processing (Kafka Streams)
- Rust + WASM frontend
- Docker-containerized stack
- Pluggable JSON vs MessagePack serialization
Prereqs: Docker, Docker Compose, Git
-
Clone
git clone <repo> && cd <repo>
-
Create
.envin each service folder:-
gmg_backend/.env
GMG_BACKEND_KAFKA_BROKERS=redpanda:9093 GMG_BACKEND_AUTH_PASSWORD=your_password
-
gmg_processor/.env
GMG_PROCESSOR_REDIS_URL=redis://redis:6379 GMG_PROCESSOR_KAFKA_BROKERS=redpanda:9093 # R2 (optional you'll need localstack or S3): GMG_PROCESSOR_R2_ACCESS_KEY_ID=… GMG_PROCESSOR_R2_ACCESS_KEY_SECRET=… GMG_PROCESSOR_R2_ACCOUNT_ID=… GMG_PROCESSOR_R2_BUCKET=your-bucket
-
gmg_frontend/.env
GMG_FRONTEND_CONFIG_PATH=../config/frontend/dev.yaml GMG_FRONTEND_PORT=8080
-
gmg_stream/.env
GMG_STREAM_KAFKA_BROKERS=redpanda:9093
-
-
Start
sudo ./run-docker.sh
- Choose serialization (
json/rmp) - Choose mode (
fullorinfra) - Rebuild? (
y/N)
Or skip prompts with defaults (JSON + infra):
sudo ./run-docker.sh --default
- Choose serialization (
-
Access http://localhost:8080
-
Initialize
curl -u any_user:your_password \ -F "file=@config/resources/example.mid" \ http://localhost:3000/initialize -
Play Refresh UI → click/right-click digits → watch sections unlock & play.
-
Stop
sudo docker compose down
With Rust, Cargo, JDK 21+, Gradle installed:
./build.sh
# Ensure Redpanda & Redis are up (e.g. via run-docker.sh --default)
# Then adapt and run ./run.sh (uses tmux)- Env vars (
.envper service + Docker Compose). - Frontend YAML in
config/frontend/*.yaml(selected viaGMG_FRONTEND_CONFIG_PATH). - Serialization toggled at startup (
jsonvsrmp).