← All work
CompletedCourse project · system-level quality assurance

QuickBite

A food-ordering system where the whole order workflow runs on events.

BunFastifyRabbitMQPostgreSQLRedisDockerNginx

The problem

Why it exists

The brief was a system-level QA project, so the CRUD part was never the point. The point was what happens between services. An order is placed, the kitchen accepts it and marks it ready, the customer is notified, and no service ever calls another directly.

That's where the real questions are. What if the broker is down when an order is saved? What if a handler fails halfway? What if the same event arrives twice? QuickBite is built around those questions, and the tests run against real infrastructure to check the answers.

How it works

One order, from click to notification

Step 1 of 8 · Client

Place order

The customer places an order in the React UI. nginx is the only public entry, and it passes /api on to the gateway.

Decisions

What I chose, and what I didn't

One database per service

Each service owns its own Postgres database and never reads or writes another's tables. A service can change its schema without asking anyone, and a slow query in one can't lock another.

Rejected

One shared database. Joins get easier, and every service quietly starts depending on every other service's tables.

A transactional outbox for events

The order and its order.placed event are written in the same transaction, and a poller publishes the event afterwards. If RabbitMQ is down, the event waits in the outbox instead of disappearing.

Rejected

Save, then publish. If the publish fails after the commit, the order exists and nobody ever hears about it.

Retry with backoff, then dead-letter

A failing handler gets three retries, after 1, 2 and 4 seconds, through a dead-letter-based delay queue. After that it lands in a permanent dead-letter queue where someone can inspect it.

Rejected

Requeueing immediately on failure. A message that can never succeed spins in a tight loop forever.

nginx is the only way in

RabbitMQ, Postgres, Redis, the gateway and the frontend are only reachable on the internal Docker network. The whole system exposes exactly one address, on port 80.

Rejected

Publishing every container's port for convenience, and then having to remember to close them all.

By the numbers

What it adds up to

services behind one gateway, one database each
5
command to run everything: docker compose up
1
retries with backoff before the dead-letter queue
3
infrastructure mocks in the integration and e2e tests
0

What I learned

The interesting failures live between services, not inside them. One service on its own is easy to test. The questions that took real thought were about the gaps: the moment after a commit and before a publish, the second delivery of the same event, the broker that's down for thirty seconds.