From 821b5cd42c3dcb662b9a80ecf374f15a64f7d0dd Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Mon, 16 Jun 2025 02:09:05 -0500 Subject: [PATCH 01/20] feat: wheres-my-pizza --- wheres-my-pizza/ABSTRACT.md | 20 ++++ wheres-my-pizza/QUESTIONS.md | 160 +++++++++++++++++++++++++ wheres-my-pizza/README.md | 223 +++++++++++++++++++++++++++++++++++ 3 files changed, 403 insertions(+) create mode 100644 wheres-my-pizza/ABSTRACT.md create mode 100644 wheres-my-pizza/QUESTIONS.md create mode 100644 wheres-my-pizza/README.md diff --git a/wheres-my-pizza/ABSTRACT.md b/wheres-my-pizza/ABSTRACT.md new file mode 100644 index 0000000..5a2614e --- /dev/null +++ b/wheres-my-pizza/ABSTRACT.md @@ -0,0 +1,20 @@ +# wheres-my-pizza + +## Learning Objectives + +- Event-driven architecture +- RabbitMQ: work-queue, fan-out, dead-letter / retry queues, manual **ACK/NACK + QoS** +- Exchange types & routing keys +- Transactional Outbox pattern (**events** table) + PostgreSQL +- Idempotent message processing (Inbox tables) +- Docker-Compose orchestration +- Unit, contract & end-to-end testing + +--- + +## Abstract + +Build **wheres-my-pizza**, a three-service Go application that tracks a pizza order from placement to delivery. +All inter-service communication happens through RabbitMQ events—**no synchronous HTTP**. +A single transactional outbox keeps the DB and broker consistent; consumers stay idempotent via inbox tables. +The public REST API is **read-only** and lets clients query order status and full event history. diff --git a/wheres-my-pizza/QUESTIONS.md b/wheres-my-pizza/QUESTIONS.md new file mode 100644 index 0000000..9ba5409 --- /dev/null +++ b/wheres-my-pizza/QUESTIONS.md @@ -0,0 +1,160 @@ +## Architecture (Hexagon / Ports & Adapters) + +### Services are split into inbound/outbound ports and pure domain packages +- [ ] Yes +- [ ] No + +### `api-gateway`, `kitchen`, `delivery` are built as **three independent Go binaries** +- [ ] Yes +- [ ] No + +### No import cycles: upper layers never import lower ones +- [ ] Yes +- [ ] No + +### All third-party tech (`pgx`, `amqp091-go`) are wrapped in adapters +- [ ] Yes +- [ ] No + +## PostgreSQL & Transactional Outbox + +### `init.sql` creates every table, enum `order_status`, indexes +- [ ] Yes +- [ ] No + +### Order insert **and** first outbox event happen in one SQL transaction +- [ ] Yes +- [ ] No + +### Unpublished events are selected with `FOR UPDATE SKIP LOCKED` +- [ ] Yes +- [ ] No + +### `published_at` is set only after broker **ACK** +- [ ] Yes +- [ ] No + +### Inbox tables use `PRIMARY KEY(event_id)` + `ON CONFLICT DO NOTHING` +- [ ] Yes +- [ ] No + +## RabbitMQ – Topology, QoS, Retry, DLQ + +### Exchanges / queues exactly match the declared topology +- [ ] Yes +- [ ] No + +### Prefetch is applied via `basic.Qos(, 0, true)` +- [ ] Yes +- [ ] No + +### NACK → retry → TTL → source-exchange path works as specified +- [ ] Yes +- [ ] No + +### Separate retry exchange **or** routing-key namespace for commands vs events +- [ ] Yes +- [ ] No + +### After max retries the message is routed to `orders.dlq.q` +- [ ] Yes +- [ ] No + +### All publishes use **Publisher Confirms** +- [ ] Yes +- [ ] No + +## HTTP API + +### `POST /orders` and `GET /orders/{id}` implemented +- [ ] Yes +- [ ] No + +### Error responses are JSON with `"error"` field +- [ ] Yes +- [ ] No + +### Query param `force_payment_fail` accepted **only in DEV** +- [ ] Yes +- [ ] No + +### API never leaks sensitive data (PII) in payloads +- [ ] Yes +- [ ] No + +## Configuration / Deployment + +### All settings come from **environment variables**; only `--help` flag exists +- [ ] Yes +- [ ] No + +### `docker compose up` boots everything; app works end-to-end +- [ ] Yes +- [ ] No + +### Services handle `SIGINT`/`SIGTERM`, drain consumers and finish gracefully +- [ ] Yes +- [ ] No + +### Startup errors exit with non-zero code and clear message +- [ ] Yes +- [ ] No + +## Logging & Observability + +### `log/slog` used with structured fields (`order_id`, `event`) +- [ ] Yes +- [ ] No + +### Log levels: **Info** on publish / **Warn** on retry / **Error** on DLQ +- [ ] Yes +- [ ] No + +### Prometheus/expvar metrics expose DLQ rate and publish latency +- [ ] Yes +- [ ] No + +## Testing + +### Unit tests cover pure business logic (no RabbitMQ / PG) +- [ ] Yes +- [ ] No + +### Contract tests validate JSON schema of events +- [ ] Yes +- [ ] No + +### End-to-end tests (Testcontainers-go) cover all 5 required scenarios +- [ ] Yes +- [ ] No + +### Retry TTL is overridden ≤ 100 ms; full `go test ./...` finishes < 30 s +- [ ] Yes +- [ ] No + +## Code Quality + +### Passes `gofumpt`; uses only stdlib + `amqp091-go` + `pgx` +- [ ] Yes +- [ ] No + +### No cyclic imports or unnecessary global state +- [ ] Yes +- [ ] No + +## Documentation + +### README contains the provided **Mermaid** topology diagram +- [ ] Yes +- [ ] No + +### Steps to reproduce locally are clear and complete +- [ ] Yes +- [ ] No + + +## Detailed Feedback + +### What was great? What you liked the most about the program and the team performance? + +### What could be better? How those improvements could positively impact the outcome? \ No newline at end of file diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md new file mode 100644 index 0000000..02a447f --- /dev/null +++ b/wheres-my-pizza/README.md @@ -0,0 +1,223 @@ +# wheres-my-pizza + +## Learning Objectives + +- Event-driven architecture +- RabbitMQ: work-queue, fan-out, dead-letter / retry queues, manual **ACK/NACK + QoS** +- Exchange types & routing keys +- Transactional Outbox pattern (**events** table) + PostgreSQL +- Idempotent message processing (Inbox tables) +- Docker-Compose orchestration +- Unit, contract & end-to-end testing + +--- + +## Abstract + +Build **wheres-my-pizza**, a three-service Go application that tracks a pizza order from placement to delivery. +All inter-service communication happens through RabbitMQ events—**no synchronous HTTP**. +A single transactional outbox keeps the DB and broker consistent; consumers stay idempotent via inbox tables. +The public REST API is **read-only** and lets clients query order status and full event history. + +--- + +## Context + +HTTP chains between micro-services quickly create tight coupling, cascading time-outs and clumsy retries. +Message brokers decouple services, buffer spikes, and let each bounded context fail or scale in isolation. +Pizza’s natural flow—**Order Placed -> Kitchen -> Delivery**—is perfect for visualising event-driven patterns. + +--- + +## Resources + +- RabbitMQ tutorials – [https://www.rabbitmq.com/getstarted.html](https://www.rabbitmq.com/getstarted.html) +- Transactional Outbox pattern – [https://microservices.io/patterns/data/transactional-outbox.html](https://microservices.io/patterns/data/transactional-outbox.html) +- RabbitMQ reliability & DLX – [https://www.rabbitmq.com/reliability.html](https://www.rabbitmq.com/reliability.html) +- Idempotent consumer pattern – [https://microservices.io/patterns/communication-style/idempotent-consumer.html](https://microservices.io/patterns/communication-style/idempotent-consumer.html) +- AMQP 0-9-1 Go client – [https://github.com/rabbitmq/amqp091-go](https://github.com/rabbitmq/amqp091-go) +- pgx PostgreSQL driver – [https://github.com/jackc/pgx](https://github.com/jackc/pgx) + +--- + +## General Criteria + +| Rule | Requirement | +| ------------------ | -------------------------------------------------------------------------------------------------------------- | +| **Code style** | Must pass `gofumpt`. | +| **Dependencies** | **Only** stdlib + `amqp091-go` + `pgx/v5`. | +| **Configuration** | All settings come from **environment variables** (ports, DSN, prefetch, exchange / queue names). **No flags**. | +| **Docker-Compose** | `docker compose up` spins RabbitMQ, PostgreSQL and three services; `docker compose down -v` cleans everything. | +| **Testing** | Unit, contract **and** e2e tests cover outbox publish, retry/DLQ flow and idempotency. | + +--- + +## Mandatory Part + +### 1. Baseline + +You will create a hexagon-inspired solution consisting of three Go services and one PostgreSQL schema. + +#### Outcomes + +- **init.sql** defining: + + | Table / Enum | Fields & Purpose | + | ------------------- | ----------------------------------------------------------------------------------------------- | + | `order_status` enum | `placed`, `baked`, `boxed`, `payment_failed`, `delivered`, `cancelled` | + | `orders` | `id` (UUID PK), `status`, `pizza`, timestamptz | + | `events` (outbox) | `id` (UUID PK), `order_id` FK, `type`, `payload` (JSONB), `created_at`, `published_at` nullable | + | `kitchen_inbox` | processed `events.id` for **kitchen** deduplication | + | `delivery_inbox` | processed `events.id` for **delivery** deduplication | + + Add sensible indexes (`orders(status)`, `events(order_id)`, GIN on `events.payload`). + +- **Service boundaries** + + | Service | Port | Responsibilities | + | ------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `api-gateway` | 8080 | `POST /orders` (insert order **+** outbox event atomically). `GET /orders/{id}` (order row + history). Background poller selects unpublished events, publishes, then sets `published_at` _after_ broker **ACK** | + | `kitchen` | — | Consumes `order.placed` -> emits `Baked` and `Boxed`. Uses `kitchen_inbox`. Manual ACK/NACK, prefetch read from env (default 20). | + | `delivery` | — | Consumes `order.boxed` -> attempts payment -> emits `Delivered` **or** `PaymentFailed`. Uses `delivery_inbox`. On `PaymentFailed` the api-gateway issues `OrderCancelled`. | + +- **RabbitMQ topology** + + | Name | Kind / Args | Notes | + | ------------------ | -------------------------- | --------------------------- | + | `orders.cmd.ex` | `direct` | Kitchen work-queue exchange | + | `orders.events.ex` | `fanout` | Broadcast domain events | + | `orders.retry.ex` | `direct` | Shared retry exchange | + | `orders.cmd.q` | DLX -> `orders.retry.ex` | Commands to kitchen | + | `orders.events.q` | DLX -> `orders.retry.ex` | Events to delivery | + | `orders.retry.q` | TTL 10 s, DLX -> source ex | Automatic delayed retry | + | `orders.dlq.q` | Dead-letter sink | Poison messages | + +```mermaid +flowchart LR + subgraph RabbitMQ + orders_cmd_ex["orders.cmd.ex\n(direct)"] + orders_events_ex["orders.events.ex\n(fanout)"] + orders_retry_ex["orders.retry.ex\n(direct)"] + + orders_cmd_q["orders.cmd.q"] + orders_events_q["orders.events.q"] + orders_retry_q["orders.retry.q\nTTL 10 s"] + orders_dlq_q["orders.dlq.q"] + end + + subgraph Services + API["api-gateway"] + Kitchen + Delivery + end + + Client["Client"] -->|POST /orders| API + API -->|OrderPlaced| orders_cmd_ex + orders_cmd_ex --> orders_cmd_q + orders_cmd_q --> Kitchen + + Kitchen -->|Baked / Boxed| orders_events_ex + orders_events_ex --> orders_events_q + orders_events_q --> Delivery + + Delivery -->|Delivered| orders_events_ex + Delivery -->|PaymentFailed| orders_events_ex + API -.->|OrderCancelled| orders_events_ex + + orders_cmd_q -- DLX --> orders_retry_ex + orders_events_q -- DLX --> orders_retry_ex + orders_retry_ex --> orders_retry_q + orders_retry_q -- TTL --> orders_cmd_ex + orders_retry_q -- TTL --> orders_events_ex + + %% максимальное число повторов -> DLQ + orders_retry_q -->|> maxRetries| orders_dlq_q + orders_cmd_q -->|Poison| orders_dlq_q + orders_events_q -->|Poison| orders_dlq_q +``` + +Routing keys follow `order.` (e.g. `order.placed`, `order.boxed`, `order.delivered`). +**ACK/NACK policy**: + +1. Success -> ACK +2. Retryable error -> NACK `requeue=false` (first passes through `orders.retry.q`) +3. Poison message -> NACK `requeue=false` -> DLQ + +- **API endpoints** + + | Method & Path | Description | + | ------------------- | ------------------------------------------------------------ | + | `POST /orders` | Body `{ "pizza": "Margherita", "force_payment_fail": true }` | + | `GET /orders/{id}` | Returns order row plus ordered event history | + +### 2. Validation Scenarios (must be covered by e2e tests) + +1. Happy path — Order goes **Placed -> Boxed -> Delivered** +2. Kitchen outage — stop `kitchen`, post order, start again; retry flow delivers event +3. Forced payment failure — `force_payment_fail=true` ends in `OrderCancelled` +4. Poison message lands in `orders.dlq.q`; consumers keep working +5. History check — `GET /orders/{id}` always returns complete timeline + +### 3. Configuration & Logging + +- Read env-vars: + + ```env + DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME + RMQ_URL, RMQ_PREFETCH + HTTP_PORT (api-gateway only) + ``` + +- Use `log/slog`. Log **Info** when publishing events, **Warning** on retries, **Error** on DLQ sends. + +- Exit non-zero if startup fails; otherwise handle errors gracefully. + +### 4. Shutdown + +Trap `SIGINT`/`SIGTERM`, drain consumers, wait for in-flight DB or broker operations, then exit. + +### 5. Usage + +Example for gateway: + +```sh +$ ./api-gateway + +Environment: + DB_* PostgreSQL connection + RMQ_URL AMQP connection URI + ... +``` + +--- + +## Support + +Focus first on **outbox -> RabbitMQ -> inbox** happy path. +Once solid, add retry/DLQ, then history API, then tests. + +Good luck. + +--- + +## Guidelines from Author + +1. Implement database schema and outbox publisher. +2. Wire minimal consumer in `kitchen`; emit `Boxed`. +3. Add `delivery` with payment simulation. +4. Introduce retry exchange & DLQ. +5. Write e2e tests with **Testcontainers-go**. + +--- + +## Author + +This project has been created by: + +Yelnar Moldabekov + +Contacts: + +- Email: [mranle91@gmail.com](mailto:mranle91@gmail.com/) +- [GitHub](https://github.com/ymoldabe/) +- [LinkedIn](https://www.linkedin.com/in/yelnar-m/) From 025781f536cea095af2c7c5d90128c60ecee864d Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Mon, 16 Jun 2025 02:11:01 -0500 Subject: [PATCH 02/20] feat: main --- wheres-my-pizza/ABSTRACT.md | 20 ---- wheres-my-pizza/QUESTIONS.md | 160 ------------------------- wheres-my-pizza/README.md | 223 ----------------------------------- 3 files changed, 403 deletions(-) delete mode 100644 wheres-my-pizza/ABSTRACT.md delete mode 100644 wheres-my-pizza/QUESTIONS.md delete mode 100644 wheres-my-pizza/README.md diff --git a/wheres-my-pizza/ABSTRACT.md b/wheres-my-pizza/ABSTRACT.md deleted file mode 100644 index 5a2614e..0000000 --- a/wheres-my-pizza/ABSTRACT.md +++ /dev/null @@ -1,20 +0,0 @@ -# wheres-my-pizza - -## Learning Objectives - -- Event-driven architecture -- RabbitMQ: work-queue, fan-out, dead-letter / retry queues, manual **ACK/NACK + QoS** -- Exchange types & routing keys -- Transactional Outbox pattern (**events** table) + PostgreSQL -- Idempotent message processing (Inbox tables) -- Docker-Compose orchestration -- Unit, contract & end-to-end testing - ---- - -## Abstract - -Build **wheres-my-pizza**, a three-service Go application that tracks a pizza order from placement to delivery. -All inter-service communication happens through RabbitMQ events—**no synchronous HTTP**. -A single transactional outbox keeps the DB and broker consistent; consumers stay idempotent via inbox tables. -The public REST API is **read-only** and lets clients query order status and full event history. diff --git a/wheres-my-pizza/QUESTIONS.md b/wheres-my-pizza/QUESTIONS.md deleted file mode 100644 index 9ba5409..0000000 --- a/wheres-my-pizza/QUESTIONS.md +++ /dev/null @@ -1,160 +0,0 @@ -## Architecture (Hexagon / Ports & Adapters) - -### Services are split into inbound/outbound ports and pure domain packages -- [ ] Yes -- [ ] No - -### `api-gateway`, `kitchen`, `delivery` are built as **three independent Go binaries** -- [ ] Yes -- [ ] No - -### No import cycles: upper layers never import lower ones -- [ ] Yes -- [ ] No - -### All third-party tech (`pgx`, `amqp091-go`) are wrapped in adapters -- [ ] Yes -- [ ] No - -## PostgreSQL & Transactional Outbox - -### `init.sql` creates every table, enum `order_status`, indexes -- [ ] Yes -- [ ] No - -### Order insert **and** first outbox event happen in one SQL transaction -- [ ] Yes -- [ ] No - -### Unpublished events are selected with `FOR UPDATE SKIP LOCKED` -- [ ] Yes -- [ ] No - -### `published_at` is set only after broker **ACK** -- [ ] Yes -- [ ] No - -### Inbox tables use `PRIMARY KEY(event_id)` + `ON CONFLICT DO NOTHING` -- [ ] Yes -- [ ] No - -## RabbitMQ – Topology, QoS, Retry, DLQ - -### Exchanges / queues exactly match the declared topology -- [ ] Yes -- [ ] No - -### Prefetch is applied via `basic.Qos(, 0, true)` -- [ ] Yes -- [ ] No - -### NACK → retry → TTL → source-exchange path works as specified -- [ ] Yes -- [ ] No - -### Separate retry exchange **or** routing-key namespace for commands vs events -- [ ] Yes -- [ ] No - -### After max retries the message is routed to `orders.dlq.q` -- [ ] Yes -- [ ] No - -### All publishes use **Publisher Confirms** -- [ ] Yes -- [ ] No - -## HTTP API - -### `POST /orders` and `GET /orders/{id}` implemented -- [ ] Yes -- [ ] No - -### Error responses are JSON with `"error"` field -- [ ] Yes -- [ ] No - -### Query param `force_payment_fail` accepted **only in DEV** -- [ ] Yes -- [ ] No - -### API never leaks sensitive data (PII) in payloads -- [ ] Yes -- [ ] No - -## Configuration / Deployment - -### All settings come from **environment variables**; only `--help` flag exists -- [ ] Yes -- [ ] No - -### `docker compose up` boots everything; app works end-to-end -- [ ] Yes -- [ ] No - -### Services handle `SIGINT`/`SIGTERM`, drain consumers and finish gracefully -- [ ] Yes -- [ ] No - -### Startup errors exit with non-zero code and clear message -- [ ] Yes -- [ ] No - -## Logging & Observability - -### `log/slog` used with structured fields (`order_id`, `event`) -- [ ] Yes -- [ ] No - -### Log levels: **Info** on publish / **Warn** on retry / **Error** on DLQ -- [ ] Yes -- [ ] No - -### Prometheus/expvar metrics expose DLQ rate and publish latency -- [ ] Yes -- [ ] No - -## Testing - -### Unit tests cover pure business logic (no RabbitMQ / PG) -- [ ] Yes -- [ ] No - -### Contract tests validate JSON schema of events -- [ ] Yes -- [ ] No - -### End-to-end tests (Testcontainers-go) cover all 5 required scenarios -- [ ] Yes -- [ ] No - -### Retry TTL is overridden ≤ 100 ms; full `go test ./...` finishes < 30 s -- [ ] Yes -- [ ] No - -## Code Quality - -### Passes `gofumpt`; uses only stdlib + `amqp091-go` + `pgx` -- [ ] Yes -- [ ] No - -### No cyclic imports or unnecessary global state -- [ ] Yes -- [ ] No - -## Documentation - -### README contains the provided **Mermaid** topology diagram -- [ ] Yes -- [ ] No - -### Steps to reproduce locally are clear and complete -- [ ] Yes -- [ ] No - - -## Detailed Feedback - -### What was great? What you liked the most about the program and the team performance? - -### What could be better? How those improvements could positively impact the outcome? \ No newline at end of file diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md deleted file mode 100644 index 02a447f..0000000 --- a/wheres-my-pizza/README.md +++ /dev/null @@ -1,223 +0,0 @@ -# wheres-my-pizza - -## Learning Objectives - -- Event-driven architecture -- RabbitMQ: work-queue, fan-out, dead-letter / retry queues, manual **ACK/NACK + QoS** -- Exchange types & routing keys -- Transactional Outbox pattern (**events** table) + PostgreSQL -- Idempotent message processing (Inbox tables) -- Docker-Compose orchestration -- Unit, contract & end-to-end testing - ---- - -## Abstract - -Build **wheres-my-pizza**, a three-service Go application that tracks a pizza order from placement to delivery. -All inter-service communication happens through RabbitMQ events—**no synchronous HTTP**. -A single transactional outbox keeps the DB and broker consistent; consumers stay idempotent via inbox tables. -The public REST API is **read-only** and lets clients query order status and full event history. - ---- - -## Context - -HTTP chains between micro-services quickly create tight coupling, cascading time-outs and clumsy retries. -Message brokers decouple services, buffer spikes, and let each bounded context fail or scale in isolation. -Pizza’s natural flow—**Order Placed -> Kitchen -> Delivery**—is perfect for visualising event-driven patterns. - ---- - -## Resources - -- RabbitMQ tutorials – [https://www.rabbitmq.com/getstarted.html](https://www.rabbitmq.com/getstarted.html) -- Transactional Outbox pattern – [https://microservices.io/patterns/data/transactional-outbox.html](https://microservices.io/patterns/data/transactional-outbox.html) -- RabbitMQ reliability & DLX – [https://www.rabbitmq.com/reliability.html](https://www.rabbitmq.com/reliability.html) -- Idempotent consumer pattern – [https://microservices.io/patterns/communication-style/idempotent-consumer.html](https://microservices.io/patterns/communication-style/idempotent-consumer.html) -- AMQP 0-9-1 Go client – [https://github.com/rabbitmq/amqp091-go](https://github.com/rabbitmq/amqp091-go) -- pgx PostgreSQL driver – [https://github.com/jackc/pgx](https://github.com/jackc/pgx) - ---- - -## General Criteria - -| Rule | Requirement | -| ------------------ | -------------------------------------------------------------------------------------------------------------- | -| **Code style** | Must pass `gofumpt`. | -| **Dependencies** | **Only** stdlib + `amqp091-go` + `pgx/v5`. | -| **Configuration** | All settings come from **environment variables** (ports, DSN, prefetch, exchange / queue names). **No flags**. | -| **Docker-Compose** | `docker compose up` spins RabbitMQ, PostgreSQL and three services; `docker compose down -v` cleans everything. | -| **Testing** | Unit, contract **and** e2e tests cover outbox publish, retry/DLQ flow and idempotency. | - ---- - -## Mandatory Part - -### 1. Baseline - -You will create a hexagon-inspired solution consisting of three Go services and one PostgreSQL schema. - -#### Outcomes - -- **init.sql** defining: - - | Table / Enum | Fields & Purpose | - | ------------------- | ----------------------------------------------------------------------------------------------- | - | `order_status` enum | `placed`, `baked`, `boxed`, `payment_failed`, `delivered`, `cancelled` | - | `orders` | `id` (UUID PK), `status`, `pizza`, timestamptz | - | `events` (outbox) | `id` (UUID PK), `order_id` FK, `type`, `payload` (JSONB), `created_at`, `published_at` nullable | - | `kitchen_inbox` | processed `events.id` for **kitchen** deduplication | - | `delivery_inbox` | processed `events.id` for **delivery** deduplication | - - Add sensible indexes (`orders(status)`, `events(order_id)`, GIN on `events.payload`). - -- **Service boundaries** - - | Service | Port | Responsibilities | - | ------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | `api-gateway` | 8080 | `POST /orders` (insert order **+** outbox event atomically). `GET /orders/{id}` (order row + history). Background poller selects unpublished events, publishes, then sets `published_at` _after_ broker **ACK** | - | `kitchen` | — | Consumes `order.placed` -> emits `Baked` and `Boxed`. Uses `kitchen_inbox`. Manual ACK/NACK, prefetch read from env (default 20). | - | `delivery` | — | Consumes `order.boxed` -> attempts payment -> emits `Delivered` **or** `PaymentFailed`. Uses `delivery_inbox`. On `PaymentFailed` the api-gateway issues `OrderCancelled`. | - -- **RabbitMQ topology** - - | Name | Kind / Args | Notes | - | ------------------ | -------------------------- | --------------------------- | - | `orders.cmd.ex` | `direct` | Kitchen work-queue exchange | - | `orders.events.ex` | `fanout` | Broadcast domain events | - | `orders.retry.ex` | `direct` | Shared retry exchange | - | `orders.cmd.q` | DLX -> `orders.retry.ex` | Commands to kitchen | - | `orders.events.q` | DLX -> `orders.retry.ex` | Events to delivery | - | `orders.retry.q` | TTL 10 s, DLX -> source ex | Automatic delayed retry | - | `orders.dlq.q` | Dead-letter sink | Poison messages | - -```mermaid -flowchart LR - subgraph RabbitMQ - orders_cmd_ex["orders.cmd.ex\n(direct)"] - orders_events_ex["orders.events.ex\n(fanout)"] - orders_retry_ex["orders.retry.ex\n(direct)"] - - orders_cmd_q["orders.cmd.q"] - orders_events_q["orders.events.q"] - orders_retry_q["orders.retry.q\nTTL 10 s"] - orders_dlq_q["orders.dlq.q"] - end - - subgraph Services - API["api-gateway"] - Kitchen - Delivery - end - - Client["Client"] -->|POST /orders| API - API -->|OrderPlaced| orders_cmd_ex - orders_cmd_ex --> orders_cmd_q - orders_cmd_q --> Kitchen - - Kitchen -->|Baked / Boxed| orders_events_ex - orders_events_ex --> orders_events_q - orders_events_q --> Delivery - - Delivery -->|Delivered| orders_events_ex - Delivery -->|PaymentFailed| orders_events_ex - API -.->|OrderCancelled| orders_events_ex - - orders_cmd_q -- DLX --> orders_retry_ex - orders_events_q -- DLX --> orders_retry_ex - orders_retry_ex --> orders_retry_q - orders_retry_q -- TTL --> orders_cmd_ex - orders_retry_q -- TTL --> orders_events_ex - - %% максимальное число повторов -> DLQ - orders_retry_q -->|> maxRetries| orders_dlq_q - orders_cmd_q -->|Poison| orders_dlq_q - orders_events_q -->|Poison| orders_dlq_q -``` - -Routing keys follow `order.` (e.g. `order.placed`, `order.boxed`, `order.delivered`). -**ACK/NACK policy**: - -1. Success -> ACK -2. Retryable error -> NACK `requeue=false` (first passes through `orders.retry.q`) -3. Poison message -> NACK `requeue=false` -> DLQ - -- **API endpoints** - - | Method & Path | Description | - | ------------------- | ------------------------------------------------------------ | - | `POST /orders` | Body `{ "pizza": "Margherita", "force_payment_fail": true }` | - | `GET /orders/{id}` | Returns order row plus ordered event history | - -### 2. Validation Scenarios (must be covered by e2e tests) - -1. Happy path — Order goes **Placed -> Boxed -> Delivered** -2. Kitchen outage — stop `kitchen`, post order, start again; retry flow delivers event -3. Forced payment failure — `force_payment_fail=true` ends in `OrderCancelled` -4. Poison message lands in `orders.dlq.q`; consumers keep working -5. History check — `GET /orders/{id}` always returns complete timeline - -### 3. Configuration & Logging - -- Read env-vars: - - ```env - DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME - RMQ_URL, RMQ_PREFETCH - HTTP_PORT (api-gateway only) - ``` - -- Use `log/slog`. Log **Info** when publishing events, **Warning** on retries, **Error** on DLQ sends. - -- Exit non-zero if startup fails; otherwise handle errors gracefully. - -### 4. Shutdown - -Trap `SIGINT`/`SIGTERM`, drain consumers, wait for in-flight DB or broker operations, then exit. - -### 5. Usage - -Example for gateway: - -```sh -$ ./api-gateway - -Environment: - DB_* PostgreSQL connection - RMQ_URL AMQP connection URI - ... -``` - ---- - -## Support - -Focus first on **outbox -> RabbitMQ -> inbox** happy path. -Once solid, add retry/DLQ, then history API, then tests. - -Good luck. - ---- - -## Guidelines from Author - -1. Implement database schema and outbox publisher. -2. Wire minimal consumer in `kitchen`; emit `Boxed`. -3. Add `delivery` with payment simulation. -4. Introduce retry exchange & DLQ. -5. Write e2e tests with **Testcontainers-go**. - ---- - -## Author - -This project has been created by: - -Yelnar Moldabekov - -Contacts: - -- Email: [mranle91@gmail.com](mailto:mranle91@gmail.com/) -- [GitHub](https://github.com/ymoldabe/) -- [LinkedIn](https://www.linkedin.com/in/yelnar-m/) From e719282210a4eab274a27787fae3455d04cebd4c Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Mon, 16 Jun 2025 02:11:25 -0500 Subject: [PATCH 03/20] feat: added wheres-my-pizza project --- wheres-my-pizza/ABSTRACT.md | 20 ++++ wheres-my-pizza/QUESTIONS.md | 160 +++++++++++++++++++++++++ wheres-my-pizza/README.md | 223 +++++++++++++++++++++++++++++++++++ 3 files changed, 403 insertions(+) create mode 100644 wheres-my-pizza/ABSTRACT.md create mode 100644 wheres-my-pizza/QUESTIONS.md create mode 100644 wheres-my-pizza/README.md diff --git a/wheres-my-pizza/ABSTRACT.md b/wheres-my-pizza/ABSTRACT.md new file mode 100644 index 0000000..5a2614e --- /dev/null +++ b/wheres-my-pizza/ABSTRACT.md @@ -0,0 +1,20 @@ +# wheres-my-pizza + +## Learning Objectives + +- Event-driven architecture +- RabbitMQ: work-queue, fan-out, dead-letter / retry queues, manual **ACK/NACK + QoS** +- Exchange types & routing keys +- Transactional Outbox pattern (**events** table) + PostgreSQL +- Idempotent message processing (Inbox tables) +- Docker-Compose orchestration +- Unit, contract & end-to-end testing + +--- + +## Abstract + +Build **wheres-my-pizza**, a three-service Go application that tracks a pizza order from placement to delivery. +All inter-service communication happens through RabbitMQ events—**no synchronous HTTP**. +A single transactional outbox keeps the DB and broker consistent; consumers stay idempotent via inbox tables. +The public REST API is **read-only** and lets clients query order status and full event history. diff --git a/wheres-my-pizza/QUESTIONS.md b/wheres-my-pizza/QUESTIONS.md new file mode 100644 index 0000000..9ba5409 --- /dev/null +++ b/wheres-my-pizza/QUESTIONS.md @@ -0,0 +1,160 @@ +## Architecture (Hexagon / Ports & Adapters) + +### Services are split into inbound/outbound ports and pure domain packages +- [ ] Yes +- [ ] No + +### `api-gateway`, `kitchen`, `delivery` are built as **three independent Go binaries** +- [ ] Yes +- [ ] No + +### No import cycles: upper layers never import lower ones +- [ ] Yes +- [ ] No + +### All third-party tech (`pgx`, `amqp091-go`) are wrapped in adapters +- [ ] Yes +- [ ] No + +## PostgreSQL & Transactional Outbox + +### `init.sql` creates every table, enum `order_status`, indexes +- [ ] Yes +- [ ] No + +### Order insert **and** first outbox event happen in one SQL transaction +- [ ] Yes +- [ ] No + +### Unpublished events are selected with `FOR UPDATE SKIP LOCKED` +- [ ] Yes +- [ ] No + +### `published_at` is set only after broker **ACK** +- [ ] Yes +- [ ] No + +### Inbox tables use `PRIMARY KEY(event_id)` + `ON CONFLICT DO NOTHING` +- [ ] Yes +- [ ] No + +## RabbitMQ – Topology, QoS, Retry, DLQ + +### Exchanges / queues exactly match the declared topology +- [ ] Yes +- [ ] No + +### Prefetch is applied via `basic.Qos(, 0, true)` +- [ ] Yes +- [ ] No + +### NACK → retry → TTL → source-exchange path works as specified +- [ ] Yes +- [ ] No + +### Separate retry exchange **or** routing-key namespace for commands vs events +- [ ] Yes +- [ ] No + +### After max retries the message is routed to `orders.dlq.q` +- [ ] Yes +- [ ] No + +### All publishes use **Publisher Confirms** +- [ ] Yes +- [ ] No + +## HTTP API + +### `POST /orders` and `GET /orders/{id}` implemented +- [ ] Yes +- [ ] No + +### Error responses are JSON with `"error"` field +- [ ] Yes +- [ ] No + +### Query param `force_payment_fail` accepted **only in DEV** +- [ ] Yes +- [ ] No + +### API never leaks sensitive data (PII) in payloads +- [ ] Yes +- [ ] No + +## Configuration / Deployment + +### All settings come from **environment variables**; only `--help` flag exists +- [ ] Yes +- [ ] No + +### `docker compose up` boots everything; app works end-to-end +- [ ] Yes +- [ ] No + +### Services handle `SIGINT`/`SIGTERM`, drain consumers and finish gracefully +- [ ] Yes +- [ ] No + +### Startup errors exit with non-zero code and clear message +- [ ] Yes +- [ ] No + +## Logging & Observability + +### `log/slog` used with structured fields (`order_id`, `event`) +- [ ] Yes +- [ ] No + +### Log levels: **Info** on publish / **Warn** on retry / **Error** on DLQ +- [ ] Yes +- [ ] No + +### Prometheus/expvar metrics expose DLQ rate and publish latency +- [ ] Yes +- [ ] No + +## Testing + +### Unit tests cover pure business logic (no RabbitMQ / PG) +- [ ] Yes +- [ ] No + +### Contract tests validate JSON schema of events +- [ ] Yes +- [ ] No + +### End-to-end tests (Testcontainers-go) cover all 5 required scenarios +- [ ] Yes +- [ ] No + +### Retry TTL is overridden ≤ 100 ms; full `go test ./...` finishes < 30 s +- [ ] Yes +- [ ] No + +## Code Quality + +### Passes `gofumpt`; uses only stdlib + `amqp091-go` + `pgx` +- [ ] Yes +- [ ] No + +### No cyclic imports or unnecessary global state +- [ ] Yes +- [ ] No + +## Documentation + +### README contains the provided **Mermaid** topology diagram +- [ ] Yes +- [ ] No + +### Steps to reproduce locally are clear and complete +- [ ] Yes +- [ ] No + + +## Detailed Feedback + +### What was great? What you liked the most about the program and the team performance? + +### What could be better? How those improvements could positively impact the outcome? \ No newline at end of file diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md new file mode 100644 index 0000000..02a447f --- /dev/null +++ b/wheres-my-pizza/README.md @@ -0,0 +1,223 @@ +# wheres-my-pizza + +## Learning Objectives + +- Event-driven architecture +- RabbitMQ: work-queue, fan-out, dead-letter / retry queues, manual **ACK/NACK + QoS** +- Exchange types & routing keys +- Transactional Outbox pattern (**events** table) + PostgreSQL +- Idempotent message processing (Inbox tables) +- Docker-Compose orchestration +- Unit, contract & end-to-end testing + +--- + +## Abstract + +Build **wheres-my-pizza**, a three-service Go application that tracks a pizza order from placement to delivery. +All inter-service communication happens through RabbitMQ events—**no synchronous HTTP**. +A single transactional outbox keeps the DB and broker consistent; consumers stay idempotent via inbox tables. +The public REST API is **read-only** and lets clients query order status and full event history. + +--- + +## Context + +HTTP chains between micro-services quickly create tight coupling, cascading time-outs and clumsy retries. +Message brokers decouple services, buffer spikes, and let each bounded context fail or scale in isolation. +Pizza’s natural flow—**Order Placed -> Kitchen -> Delivery**—is perfect for visualising event-driven patterns. + +--- + +## Resources + +- RabbitMQ tutorials – [https://www.rabbitmq.com/getstarted.html](https://www.rabbitmq.com/getstarted.html) +- Transactional Outbox pattern – [https://microservices.io/patterns/data/transactional-outbox.html](https://microservices.io/patterns/data/transactional-outbox.html) +- RabbitMQ reliability & DLX – [https://www.rabbitmq.com/reliability.html](https://www.rabbitmq.com/reliability.html) +- Idempotent consumer pattern – [https://microservices.io/patterns/communication-style/idempotent-consumer.html](https://microservices.io/patterns/communication-style/idempotent-consumer.html) +- AMQP 0-9-1 Go client – [https://github.com/rabbitmq/amqp091-go](https://github.com/rabbitmq/amqp091-go) +- pgx PostgreSQL driver – [https://github.com/jackc/pgx](https://github.com/jackc/pgx) + +--- + +## General Criteria + +| Rule | Requirement | +| ------------------ | -------------------------------------------------------------------------------------------------------------- | +| **Code style** | Must pass `gofumpt`. | +| **Dependencies** | **Only** stdlib + `amqp091-go` + `pgx/v5`. | +| **Configuration** | All settings come from **environment variables** (ports, DSN, prefetch, exchange / queue names). **No flags**. | +| **Docker-Compose** | `docker compose up` spins RabbitMQ, PostgreSQL and three services; `docker compose down -v` cleans everything. | +| **Testing** | Unit, contract **and** e2e tests cover outbox publish, retry/DLQ flow and idempotency. | + +--- + +## Mandatory Part + +### 1. Baseline + +You will create a hexagon-inspired solution consisting of three Go services and one PostgreSQL schema. + +#### Outcomes + +- **init.sql** defining: + + | Table / Enum | Fields & Purpose | + | ------------------- | ----------------------------------------------------------------------------------------------- | + | `order_status` enum | `placed`, `baked`, `boxed`, `payment_failed`, `delivered`, `cancelled` | + | `orders` | `id` (UUID PK), `status`, `pizza`, timestamptz | + | `events` (outbox) | `id` (UUID PK), `order_id` FK, `type`, `payload` (JSONB), `created_at`, `published_at` nullable | + | `kitchen_inbox` | processed `events.id` for **kitchen** deduplication | + | `delivery_inbox` | processed `events.id` for **delivery** deduplication | + + Add sensible indexes (`orders(status)`, `events(order_id)`, GIN on `events.payload`). + +- **Service boundaries** + + | Service | Port | Responsibilities | + | ------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `api-gateway` | 8080 | `POST /orders` (insert order **+** outbox event atomically). `GET /orders/{id}` (order row + history). Background poller selects unpublished events, publishes, then sets `published_at` _after_ broker **ACK** | + | `kitchen` | — | Consumes `order.placed` -> emits `Baked` and `Boxed`. Uses `kitchen_inbox`. Manual ACK/NACK, prefetch read from env (default 20). | + | `delivery` | — | Consumes `order.boxed` -> attempts payment -> emits `Delivered` **or** `PaymentFailed`. Uses `delivery_inbox`. On `PaymentFailed` the api-gateway issues `OrderCancelled`. | + +- **RabbitMQ topology** + + | Name | Kind / Args | Notes | + | ------------------ | -------------------------- | --------------------------- | + | `orders.cmd.ex` | `direct` | Kitchen work-queue exchange | + | `orders.events.ex` | `fanout` | Broadcast domain events | + | `orders.retry.ex` | `direct` | Shared retry exchange | + | `orders.cmd.q` | DLX -> `orders.retry.ex` | Commands to kitchen | + | `orders.events.q` | DLX -> `orders.retry.ex` | Events to delivery | + | `orders.retry.q` | TTL 10 s, DLX -> source ex | Automatic delayed retry | + | `orders.dlq.q` | Dead-letter sink | Poison messages | + +```mermaid +flowchart LR + subgraph RabbitMQ + orders_cmd_ex["orders.cmd.ex\n(direct)"] + orders_events_ex["orders.events.ex\n(fanout)"] + orders_retry_ex["orders.retry.ex\n(direct)"] + + orders_cmd_q["orders.cmd.q"] + orders_events_q["orders.events.q"] + orders_retry_q["orders.retry.q\nTTL 10 s"] + orders_dlq_q["orders.dlq.q"] + end + + subgraph Services + API["api-gateway"] + Kitchen + Delivery + end + + Client["Client"] -->|POST /orders| API + API -->|OrderPlaced| orders_cmd_ex + orders_cmd_ex --> orders_cmd_q + orders_cmd_q --> Kitchen + + Kitchen -->|Baked / Boxed| orders_events_ex + orders_events_ex --> orders_events_q + orders_events_q --> Delivery + + Delivery -->|Delivered| orders_events_ex + Delivery -->|PaymentFailed| orders_events_ex + API -.->|OrderCancelled| orders_events_ex + + orders_cmd_q -- DLX --> orders_retry_ex + orders_events_q -- DLX --> orders_retry_ex + orders_retry_ex --> orders_retry_q + orders_retry_q -- TTL --> orders_cmd_ex + orders_retry_q -- TTL --> orders_events_ex + + %% максимальное число повторов -> DLQ + orders_retry_q -->|> maxRetries| orders_dlq_q + orders_cmd_q -->|Poison| orders_dlq_q + orders_events_q -->|Poison| orders_dlq_q +``` + +Routing keys follow `order.` (e.g. `order.placed`, `order.boxed`, `order.delivered`). +**ACK/NACK policy**: + +1. Success -> ACK +2. Retryable error -> NACK `requeue=false` (first passes through `orders.retry.q`) +3. Poison message -> NACK `requeue=false` -> DLQ + +- **API endpoints** + + | Method & Path | Description | + | ------------------- | ------------------------------------------------------------ | + | `POST /orders` | Body `{ "pizza": "Margherita", "force_payment_fail": true }` | + | `GET /orders/{id}` | Returns order row plus ordered event history | + +### 2. Validation Scenarios (must be covered by e2e tests) + +1. Happy path — Order goes **Placed -> Boxed -> Delivered** +2. Kitchen outage — stop `kitchen`, post order, start again; retry flow delivers event +3. Forced payment failure — `force_payment_fail=true` ends in `OrderCancelled` +4. Poison message lands in `orders.dlq.q`; consumers keep working +5. History check — `GET /orders/{id}` always returns complete timeline + +### 3. Configuration & Logging + +- Read env-vars: + + ```env + DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME + RMQ_URL, RMQ_PREFETCH + HTTP_PORT (api-gateway only) + ``` + +- Use `log/slog`. Log **Info** when publishing events, **Warning** on retries, **Error** on DLQ sends. + +- Exit non-zero if startup fails; otherwise handle errors gracefully. + +### 4. Shutdown + +Trap `SIGINT`/`SIGTERM`, drain consumers, wait for in-flight DB or broker operations, then exit. + +### 5. Usage + +Example for gateway: + +```sh +$ ./api-gateway + +Environment: + DB_* PostgreSQL connection + RMQ_URL AMQP connection URI + ... +``` + +--- + +## Support + +Focus first on **outbox -> RabbitMQ -> inbox** happy path. +Once solid, add retry/DLQ, then history API, then tests. + +Good luck. + +--- + +## Guidelines from Author + +1. Implement database schema and outbox publisher. +2. Wire minimal consumer in `kitchen`; emit `Boxed`. +3. Add `delivery` with payment simulation. +4. Introduce retry exchange & DLQ. +5. Write e2e tests with **Testcontainers-go**. + +--- + +## Author + +This project has been created by: + +Yelnar Moldabekov + +Contacts: + +- Email: [mranle91@gmail.com](mailto:mranle91@gmail.com/) +- [GitHub](https://github.com/ymoldabe/) +- [LinkedIn](https://www.linkedin.com/in/yelnar-m/) From ffa428ac813685f17b03cd8402e54aa6331a0c10 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Mon, 16 Jun 2025 07:28:17 -0500 Subject: [PATCH 04/20] fix: support --- wheres-my-pizza/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 02a447f..262083c 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -194,7 +194,8 @@ Environment: ## Support Focus first on **outbox -> RabbitMQ -> inbox** happy path. -Once solid, add retry/DLQ, then history API, then tests. +Before coding, write a minimal failing test that asserts this flow works end-to-end. +Once solid, add retry/DLQ, then history API, then expand the test suite accordingly. Good luck. @@ -202,11 +203,12 @@ Good luck. ## Guidelines from Author -1. Implement database schema and outbox publisher. -2. Wire minimal consumer in `kitchen`; emit `Boxed`. -3. Add `delivery` with payment simulation. -4. Introduce retry exchange & DLQ. -5. Write e2e tests with **Testcontainers-go**. +1. Tests first. Spin up PostgreSQL + RabbitMQ with Testcontainers-go and create unit/contract/e2e tests that should fail until each step is implemented. +2. Implement database schema and outbox publisher. +3. Wire minimal consumer in `kitchen`; emit `Boxed`. +4. Add `delivery` with payment simulation. +5. Introduce retry exchange & DLQ. +6. Write e2e tests with **Testcontainers-go**. --- From 5600c80b2d74fe14c34dfa3af3038ee08200d0d7 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Tue, 17 Jun 2025 00:28:36 -0500 Subject: [PATCH 05/20] fix: readme --- wheres-my-pizza/README.md | 512 ++++++++++++++++++++++++++------------ 1 file changed, 346 insertions(+), 166 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 262083c..5e61860 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -1,216 +1,397 @@ -# wheres-my-pizza +# Restaurant Order Management System with RabbitMQ ## Learning Objectives -- Event-driven architecture -- RabbitMQ: work-queue, fan-out, dead-letter / retry queues, manual **ACK/NACK + QoS** -- Exchange types & routing keys -- Transactional Outbox pattern (**events** table) + PostgreSQL -- Idempotent message processing (Inbox tables) -- Docker-Compose orchestration -- Unit, contract & end-to-end testing - ---- +- Message Queue Systems +- RabbitMQ Integration +- Concurrent Programming ## Abstract -Build **wheres-my-pizza**, a three-service Go application that tracks a pizza order from placement to delivery. -All inter-service communication happens through RabbitMQ events—**no synchronous HTTP**. -A single transactional outbox keeps the DB and broker consistent; consumers stay idempotent via inbox tables. -The public REST API is **read-only** and lets clients query order status and full event history. +In this project, you will build a restaurant order management system using RabbitMQ as a message broker. The system simulates a real restaurant workflow where orders go through various processing stages: received -> cooking -> ready -> delivered. + +Similar systems are used in real restaurants and food delivery services. For example, when you order food through an app, your order goes through an analogous processing system with task distribution among different staff members. ---- +This project will teach you that before you start writing code, you should think through the system architecture, understand how components will interact, and only then proceed to implementation. ## Context -HTTP chains between micro-services quickly create tight coupling, cascading time-outs and clumsy retries. -Message brokers decouple services, buffer spikes, and let each bounded context fail or scale in isolation. -Pizza’s natural flow—**Order Placed -> Kitchen -> Delivery**—is perfect for visualising event-driven patterns. +> you can’t polish your way out of bad architecture +> ---- +The challenge we've chosen may seem unique, but at its core lies the common structure of many other distributed systems: data comes in, gets processed by various components, and is passed along the chain. -## Resources +Our specific task is to create a reliable order processing system that can scale horizontally. If we simply processed orders sequentially in a single thread, the system would quickly become a bottleneck as load increases. + +To achieve more efficient results, we need a distributed architecture with separation of concerns, where each component performs its specific function. + +**Message Queue Patterns** + +A smart way to solve this type of problem is using message queue patterns. This approach views the system as a set of interacting services, where each service processes a specific type of message and passes the result further down the chain. + +**Work Queue Pattern** +- One producer sends tasks to a queue +- Multiple consumers compete to receive tasks +- Each task is processed by exactly one consumer +- Provides load distribution among workers + +**Publish/Subscribe Pattern** +- One publisher sends messages to all subscribers +- Multiple consumers receive copies of messages +- Used for notifications and state synchronization + +**Routing Pattern** +- Messages are routed based on routing key +- Allows creating complex processing schemes +- Provides flexibility in defining recipients + +Order processing algorithm using queues: + +``` +1. Client places order through API +2. Order is placed in kitchen_queue +3. Available cook takes order from queue +4. After cooking, order is directed to quality_queue +5. Quality controller checks the order +6. Ready order is placed in packaging_queue +7. Packager prepares order for delivery +8. Order is directed to courier through delivery_queue +9. All interested parties receive status notifications +``` + +**Data Structure and Architecture** + +Let's consider our program requirements. We aim to process hundreds of orders simultaneously, support multiple workers, and ensure response times in seconds, not minutes. At this scale, we cannot afford inefficient algorithms or architecture. -- RabbitMQ tutorials – [https://www.rabbitmq.com/getstarted.html](https://www.rabbitmq.com/getstarted.html) -- Transactional Outbox pattern – [https://microservices.io/patterns/data/transactional-outbox.html](https://microservices.io/patterns/data/transactional-outbox.html) -- RabbitMQ reliability & DLX – [https://www.rabbitmq.com/reliability.html](https://www.rabbitmq.com/reliability.html) -- Idempotent consumer pattern – [https://microservices.io/patterns/communication-style/idempotent-consumer.html](https://microservices.io/patterns/communication-style/idempotent-consumer.html) -- AMQP 0-9-1 Go client – [https://github.com/rabbitmq/amqp091-go](https://github.com/rabbitmq/amqp091-go) -- pgx PostgreSQL driver – [https://github.com/jackc/pgx](https://github.com/jackc/pgx) +The order processing workflow requires coordination between multiple components. This means we need a smart way to organize interaction between services. We're looking for a structure that can efficiently represent order state and its transitions between stages. ---- +Our program will work in microservice mode: each component (cook, quality controller, packager, courier) works independently but is coordinated through RabbitMQ. Each service must quickly receive appropriate tasks and send results further down the chain. + +Let's call the combination of an order and its current state a "processing stage" - this is a common term in workflow systems. We'll need to track transitions between stages and ensure reliable message delivery. + +This approach gives us a clear path forward. By focusing on these key requirements, we can design a solution that is both elegant and efficient. + +## Resources + +- [RabbitMQ Documentation](https://www.rabbitmq.com/documentation.html) +- [RabbitMQ Docker Image](https://hub.docker.com/_/rabbitmq) +- [Go AMQP Client](https://github.com/rabbitmq/amqp091-go) ## General Criteria -| Rule | Requirement | -| ------------------ | -------------------------------------------------------------------------------------------------------------- | -| **Code style** | Must pass `gofumpt`. | -| **Dependencies** | **Only** stdlib + `amqp091-go` + `pgx/v5`. | -| **Configuration** | All settings come from **environment variables** (ports, DSN, prefetch, exchange / queue names). **No flags**. | -| **Docker-Compose** | `docker compose up` spins RabbitMQ, PostgreSQL and three services; `docker compose down -v` cleans everything. | -| **Testing** | Unit, contract **and** e2e tests cover outbox publish, retry/DLQ flow and idempotency. | +- Your code MUST be written in accordance with [gofumpt](https://github.com/mvdan/gofumpt). If not, you will automatically receive a score of `0`. +- Your program MUST compile successfully. +- Your program MUST NOT crash unexpectedly (any panics: `nil-pointer dereference`, `index out of range`, etc.). If this happens, you will receive `0` points during defense. +- Only built-in Go packages and the official AMQP client (`github.com/rabbitmq/amqp091-go`) are allowed. If other packages are used, you will receive a score of `0`. +- RabbitMQ server MUST be running and available for connection. +- The project MUST compile with the following command in the project root directory: ---- +```sh +$ go build -o restaurant-system . +``` ## Mandatory Part -### 1. Baseline - -You will create a hexagon-inspired solution consisting of three Go services and one PostgreSQL schema. - -#### Outcomes - -- **init.sql** defining: - - | Table / Enum | Fields & Purpose | - | ------------------- | ----------------------------------------------------------------------------------------------- | - | `order_status` enum | `placed`, `baked`, `boxed`, `payment_failed`, `delivered`, `cancelled` | - | `orders` | `id` (UUID PK), `status`, `pizza`, timestamptz | - | `events` (outbox) | `id` (UUID PK), `order_id` FK, `type`, `payload` (JSONB), `created_at`, `published_at` nullable | - | `kitchen_inbox` | processed `events.id` for **kitchen** deduplication | - | `delivery_inbox` | processed `events.id` for **delivery** deduplication | - - Add sensible indexes (`orders(status)`, `events(order_id)`, GIN on `events.payload`). - -- **Service boundaries** - - | Service | Port | Responsibilities | - | ------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | `api-gateway` | 8080 | `POST /orders` (insert order **+** outbox event atomically). `GET /orders/{id}` (order row + history). Background poller selects unpublished events, publishes, then sets `published_at` _after_ broker **ACK** | - | `kitchen` | — | Consumes `order.placed` -> emits `Baked` and `Boxed`. Uses `kitchen_inbox`. Manual ACK/NACK, prefetch read from env (default 20). | - | `delivery` | — | Consumes `order.boxed` -> attempts payment -> emits `Delivered` **or** `PaymentFailed`. Uses `delivery_inbox`. On `PaymentFailed` the api-gateway issues `OrderCancelled`. | - -- **RabbitMQ topology** - - | Name | Kind / Args | Notes | - | ------------------ | -------------------------- | --------------------------- | - | `orders.cmd.ex` | `direct` | Kitchen work-queue exchange | - | `orders.events.ex` | `fanout` | Broadcast domain events | - | `orders.retry.ex` | `direct` | Shared retry exchange | - | `orders.cmd.q` | DLX -> `orders.retry.ex` | Commands to kitchen | - | `orders.events.q` | DLX -> `orders.retry.ex` | Events to delivery | - | `orders.retry.q` | TTL 10 s, DLX -> source ex | Automatic delayed retry | - | `orders.dlq.q` | Dead-letter sink | Poison messages | - -```mermaid -flowchart LR - subgraph RabbitMQ - orders_cmd_ex["orders.cmd.ex\n(direct)"] - orders_events_ex["orders.events.ex\n(fanout)"] - orders_retry_ex["orders.retry.ex\n(direct)"] - - orders_cmd_q["orders.cmd.q"] - orders_events_q["orders.events.q"] - orders_retry_q["orders.retry.q\nTTL 10 s"] - orders_dlq_q["orders.dlq.q"] - end - - subgraph Services - API["api-gateway"] - Kitchen - Delivery - end - - Client["Client"] -->|POST /orders| API - API -->|OrderPlaced| orders_cmd_ex - orders_cmd_ex --> orders_cmd_q - orders_cmd_q --> Kitchen - - Kitchen -->|Baked / Boxed| orders_events_ex - orders_events_ex --> orders_events_q - orders_events_q --> Delivery - - Delivery -->|Delivered| orders_events_ex - Delivery -->|PaymentFailed| orders_events_ex - API -.->|OrderCancelled| orders_events_ex - - orders_cmd_q -- DLX --> orders_retry_ex - orders_events_q -- DLX --> orders_retry_ex - orders_retry_ex --> orders_retry_q - orders_retry_q -- TTL --> orders_cmd_ex - orders_retry_q -- TTL --> orders_events_ex - - %% максимальное число повторов -> DLQ - orders_retry_q -->|> maxRetries| orders_dlq_q - orders_cmd_q -->|Poison| orders_dlq_q - orders_events_q -->|Poison| orders_dlq_q -``` +### Baseline -Routing keys follow `order.` (e.g. `order.placed`, `order.boxed`, `order.delivered`). -**ACK/NACK policy**: +By default, your program should implement a basic order processing system using RabbitMQ work queue pattern to distribute orders among cooks. -1. Success -> ACK -2. Retryable error -> NACK `requeue=false` (first passes through `orders.retry.q`) -3. Poison message -> NACK `requeue=false` -> DLQ +Outcomes: +- Program accepts orders through REST API +- Program uses RabbitMQ to store orders in queue +- Program distributes orders among available cooks +- Program handles basic error scenarios -- **API endpoints** +Notes: +- Orders contain: order ID, list of dishes, customer information, order type +- Cooks compete for orders (competing consumers) +- Default cooking time 10 seconds for demonstration - | Method & Path | Description | - | ------------------- | ------------------------------------------------------------ | - | `POST /orders` | Body `{ "pizza": "Margherita", "force_payment_fail": true }` | - | `GET /orders/{id}` | Returns order row plus ordered event history | +Constraints: +- On any error, output error message with reason specified +- Orders must be acknowledged only after successful processing +- Maximum 50 concurrent orders in system +- Each order must be processed exactly once -### 2. Validation Scenarios (must be covered by e2e tests) +Examples: -1. Happy path — Order goes **Placed -> Boxed -> Delivered** -2. Kitchen outage — stop `kitchen`, post order, start again; retry flow delivers event -3. Forced payment failure — `force_payment_fail=true` ends in `OrderCancelled` -4. Poison message lands in `orders.dlq.q`; consumers keep working -5. History check — `GET /orders/{id}` always returns complete timeline +```sh +$ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management + +# Start order service +$ ./restaurant-system --mode=order-service --port=3000 +Order service started on port 3000 +Connected to RabbitMQ at localhost:5672 +Kitchen queue initialized + +# Place order +$ curl -X POST http://localhost:3000/orders \ + -H "Content-Type: application/json" \ + -d '{ + "customer_name": "John Doe", + "phone": "555-0123", + "items": [ + {"name": "Margherita Pizza", "quantity": 1}, + {"name": "Caesar Salad", "quantity": 1} + ] + }' + +Response: {"order_id": "ORD_001", "status": "received", "message": "Order placed successfully"} + +# Cook processes orders +$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_mario" +Kitchen worker chef_mario connected to RabbitMQ +Listening for orders on kitchen_queue... +Received order ORD_001: Margherita Pizza (1), Caesar Salad (1) +Customer: John Doe (555-0123) +Processing order... (10 seconds) +Order ORD_001 completed by chef_mario +``` -### 3. Configuration & Logging +### Multiple Workers -- Read env-vars: +Your program should support multiple cooks working simultaneously with automatic order distribution among them. - ```env - DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME - RMQ_URL, RMQ_PREFETCH - HTTP_PORT (api-gateway only) - ``` +Outcomes: +- Program supports multiple cooks +- Orders are distributed evenly among available cooks +- New cooks can connect dynamically +- Cook disconnection doesn't affect processing of other orders -- Use `log/slog`. Log **Info** when publishing events, **Warning** on retries, **Error** on DLQ sends. +Constraints: +- Use RabbitMQ round-robin distribution +- Maximum 10 cooks simultaneously +- Each cook must have unique ID +- Unacknowledged messages should be redirected to other cooks -- Exit non-zero if startup fails; otherwise handle errors gracefully. +```sh +# Start multiple cooks +$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_mario" & +$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_luigi" & +$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_anna" & + +Kitchen worker chef_mario connected +Kitchen worker chef_luigi connected +Kitchen worker chef_anna connected +All workers listening on kitchen_queue + +# Monitor order distribution +$ curl -X POST http://localhost:3000/orders -d '{"customer_name":"Alice","items":[{"name":"Pizza","quantity":1}]}' +$ curl -X POST http://localhost:3000/orders -d '{"customer_name":"Bob","items":[{"name":"Pasta","quantity":1}]}' +$ curl -X POST http://localhost:3000/orders -d '{"customer_name":"Carol","items":[{"name":"Salad","quantity":1}]}' + +# In cook logs: +# chef_mario: Received order ORD_001 (Alice - Pizza) +# chef_luigi: Received order ORD_002 (Bob - Pasta) +# chef_anna: Received order ORD_003 (Carol - Salad) +``` -### 4. Shutdown +### Order Status Tracking -Trap `SIGINT`/`SIGTERM`, drain consumers, wait for in-flight DB or broker operations, then exit. +Your program should implement an order status tracking system using publish/subscribe pattern for notifications. -### 5. Usage +Outcomes: +- Program tracks status of each order +- Order status is updated at each processing stage +- Clients can query status of their orders +- System sends notifications about status changes -Example for gateway: +Constraints: +- Statuses: received, cooking, ready, delivered +- Use fanout exchange for notifications +- Notifications must contain order_id, status, timestamp +- API endpoint for checking order status ```sh -$ ./api-gateway +# Start tracking service +$ ./restaurant-system --mode=tracking-service & + +# Subscribe to order notifications +$ ./restaurant-system --mode=notification-subscriber --customer="John Doe" +Subscribed to order notifications +Received: Order ORD_001 status changed to 'cooking' at 2024-12-16 10:30:15 +Received: Order ORD_001 status changed to 'ready' at 2024-12-16 10:40:20 + +# Check order status via API +$ curl http://localhost:3000/orders/ORD_001/status +{"order_id": "ORD_001", "status": "ready", "updated_at": "2024-12-16T10:40:20Z"} +``` + +### Order Types and Routing + +Your program should support different order types (dine-in, takeout, delivery) with routing based on order type. -Environment: - DB_* PostgreSQL connection - RMQ_URL AMQP connection URI - ... +Outcomes: +- Program supports three order types: dine-in, takeout, delivery +- Orders are routed to appropriate queues based on type +- Different order types have different processing times +- Delivery orders require additional processing + +Constraints: +- Use topic exchange with routing keys: `orders.kitchen.{type}` +- Cooking times: dine-in (8 sec), takeout (10 sec), delivery (12 sec) +- Delivery orders must include address +- Order type validation on creation + +```sh +# Dine-in order +$ curl -X POST http://localhost:3000/orders \ + -H "Content-Type: application/json" \ + -d '{ + "customer_name": "John Doe", + "order_type": "dine-in", + "table_number": 5, + "items": [{"name": "Steak", "quantity": 1}] + }' + +# Takeout order +$ curl -X POST http://localhost:3000/orders \ + -H "Content-Type: application/json" \ + -d '{ + "customer_name": "Jane Smith", + "order_type": "takeout", + "items": [{"name": "Burger", "quantity": 2}] + }' + +# Delivery order +$ curl -X POST http://localhost:3000/orders \ + -H "Content-Type: application/json" \ + -d '{ + "customer_name": "Bob Wilson", + "order_type": "delivery", + "address": "123 Main St, City", + "items": [{"name": "Pizza", "quantity": 1}] + }' + +# Cook can listen to specific order types +$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_mario" --order-types="dine-in,takeout" +chef_mario listening for dine-in and takeout orders only ``` ---- +### Priority Queue + +Your program should support order prioritization using RabbitMQ priority queues. + +Outcomes: +- Program supports priority orders +- VIP clients receive high priority automatically +- Large orders (>$50) receive medium priority +- Urgent orders can be marked as priority + +Constraints: +- Priorities: 1 (normal), 5 (medium), 10 (high) +- Configure queues with x-max-priority parameter +- VIP status determined by client ID or flag +- Priority calculated when placing order + +```sh +# VIP order (high priority) +$ curl -X POST http://localhost:3000/orders \ + -H "Content-Type: application/json" \ + -d '{ + "customer_name": "VIP Customer", + "customer_type": "VIP", + "items": [{"name": "Lobster", "quantity": 1, "price": 45.99}], + "rush": true + }' + +Response: {"order_id": "ORD_002", "priority": 10, "status": "received"} + +# Large order (medium priority) +$ curl -X POST http://localhost:3000/orders \ + -H "Content-Type: application/json" \ + -d '{ + "customer_name": "Regular Customer", + "items": [ + {"name": "Pizza", "quantity": 3, "price": 15.99}, + {"name": "Salad", "quantity": 2, "price": 8.99} + ] + }' + +Response: {"order_id": "ORD_003", "priority": 5, "status": "received"} + +# Cook receives orders by priority +$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_mario" +Received HIGH PRIORITY order ORD_002 (VIP Customer - Lobster) +Received MEDIUM PRIORITY order ORD_003 (Regular Customer - Pizza x3, Salad x2) +``` + +### Usage + +Your program should output usage information. + +Outcomes: +- Program outputs text describing usage of all modes and options + +```sh +$ ./restaurant-system --help +Restaurant Order Management System with RabbitMQ + +Usage: + restaurant-system --mode=order-service [--port=N] [--rabbitmq=HOST:PORT] + restaurant-system --mode=kitchen-worker --worker-id=ID [--order-types=TYPES] [--rabbitmq=HOST:PORT] + restaurant-system --mode=tracking-service [--rabbitmq=HOST:PORT] + restaurant-system --mode=notification-subscriber [--customer=NAME] [--rabbitmq=HOST:PORT] + restaurant-system --setup-queues [--rabbitmq=HOST:PORT] + restaurant-system --help + +Service Modes: + order-service REST API for receiving orders + kitchen-worker Kitchen staff processing orders + tracking-service Order status tracking service + notification-subscriber Customer notification service + +Connection Options: + --rabbitmq RabbitMQ server address (default: localhost:5672) + --rabbitmq-user RabbitMQ username (default: guest) + --rabbitmq-pass RabbitMQ password (default: guest) + +Order Service Options: + --port HTTP port for REST API (default: 3000) + +Kitchen Worker Options: + --worker-id Unique worker identifier (required) + --order-types Comma-separated order types to process (default: all) + +Notification Options: + --customer Customer name for notifications + +Setup: + --setup-queues Initialize RabbitMQ exchanges and queues + +Examples: + # Setup RabbitMQ + ./restaurant-system --setup-queues + + # Start order service + ./restaurant-system --mode=order-service --port=3000 + + # Start kitchen worker + ./restaurant-system --mode=kitchen-worker --worker-id=chef1 + + # Monitor notifications + ./restaurant-system --mode=notification-subscriber --customer="John Doe" +``` ## Support -Focus first on **outbox -> RabbitMQ -> inbox** happy path. -Before coding, write a minimal failing test that asserts this flow works end-to-end. -Once solid, add retry/DLQ, then history API, then expand the test suite accordingly. +If you get stuck, test your code with the example inputs from the project. You should get the same results. If not, re-read the description again. Perhaps you missed something, or your code is incorrect. -Good luck. +Make sure the RabbitMQ server is running and accessible. Check the connection to RabbitMQ and proper configuration of queues and exchanges. ---- +If you're still stuck, ask a friend for help or take a break and come back later. ## Guidelines from Author -1. Tests first. Spin up PostgreSQL + RabbitMQ with Testcontainers-go and create unit/contract/e2e tests that should fail until each step is implemented. -2. Implement database schema and outbox publisher. -3. Wire minimal consumer in `kitchen`; emit `Boxed`. -4. Add `delivery` with payment simulation. -5. Introduce retry exchange & DLQ. -6. Write e2e tests with **Testcontainers-go**. +Before diving into code, it's crucial to step back and think about your system architecture. This project illustrates a fundamental principle of good software design - your architectural choices often determine the clarity and efficiency of your code. + +Start with questions: How will components interact? Which message exchange patterns best fit the task? What delivery guarantees are needed? Only after you've carefully thought through these questions should you proceed to API design and code writing. + +This approach may seem like extra work initially, but it pays off. Well-chosen architecture can make your code simpler, more readable, and often more efficient. It's like choosing the right tools before starting work - with the right foundation, the rest of the work becomes much easier. + +Remember that in programming, as in many other things, thoughtful preparation is the key to success. Spend time on proper architecture, and you'll find that the coding process becomes smoother and more enjoyable. ---- +Good system architecture is the foundation of clear, efficient code. It often simplifies programming more than clever algorithms can. Invest time in architectural design first. This approach usually leads to more maintainable and understandable programs, regardless of their size or complexity. ## Author @@ -220,6 +401,5 @@ Yelnar Moldabekov Contacts: -- Email: [mranle91@gmail.com](mailto:mranle91@gmail.com/) -- [GitHub](https://github.com/ymoldabe/) -- [LinkedIn](https://www.linkedin.com/in/yelnar-m/) +- Email: [mranle91@gmail.com](mailto:mranle91@gmail.com) +- [GitHub](https://github.com/ymoldabe/) \ No newline at end of file From 4e1f84bc255a3a75811e5c8ce3cb57c4ebee55c6 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Tue, 17 Jun 2025 00:33:16 -0500 Subject: [PATCH 06/20] finish --- wheres-my-pizza/ABSTRACT.md | 21 ++- wheres-my-pizza/QUESTIONS.md | 241 ++++++++++++++++++++++++++++------- wheres-my-pizza/README.md | 4 +- 3 files changed, 208 insertions(+), 58 deletions(-) diff --git a/wheres-my-pizza/ABSTRACT.md b/wheres-my-pizza/ABSTRACT.md index 5a2614e..38db333 100644 --- a/wheres-my-pizza/ABSTRACT.md +++ b/wheres-my-pizza/ABSTRACT.md @@ -2,19 +2,14 @@ ## Learning Objectives -- Event-driven architecture -- RabbitMQ: work-queue, fan-out, dead-letter / retry queues, manual **ACK/NACK + QoS** -- Exchange types & routing keys -- Transactional Outbox pattern (**events** table) + PostgreSQL -- Idempotent message processing (Inbox tables) -- Docker-Compose orchestration -- Unit, contract & end-to-end testing - ---- +- Message Queue Systems +- RabbitMQ Integration +- Concurrent Programming ## Abstract -Build **wheres-my-pizza**, a three-service Go application that tracks a pizza order from placement to delivery. -All inter-service communication happens through RabbitMQ events—**no synchronous HTTP**. -A single transactional outbox keeps the DB and broker consistent; consumers stay idempotent via inbox tables. -The public REST API is **read-only** and lets clients query order status and full event history. +In this project, you will build a restaurant order management system using RabbitMQ as a message broker. The system simulates a real restaurant workflow where orders go through various processing stages: received -> cooking -> ready -> delivered. + +Similar systems are used in real restaurants and food delivery services. For example, when you order food through an app, your order goes through an analogous processing system with task distribution among different staff members. + +This project will teach you that before you start writing code, you should think through the system architecture, understand how components will interact, and only then proceed to implementation. diff --git a/wheres-my-pizza/QUESTIONS.md b/wheres-my-pizza/QUESTIONS.md index 9ba5409..5a82cb3 100644 --- a/wheres-my-pizza/QUESTIONS.md +++ b/wheres-my-pizza/QUESTIONS.md @@ -1,157 +1,312 @@ -## Architecture (Hexagon / Ports & Adapters) +## Project Setup and Compilation +### Does the program compile successfully with `go build -o restaurant-system .`? +- [ ] Yes +- [ ] No + +### Does the code follow gofumpt formatting standards? +- [ ] Yes +- [ ] No + +### Does the program exit with a non-zero status code and clear error message when invalid arguments are provided? +- [ ] Yes +- [ ] No + +### Does the program handle runtime errors gracefully without crashing? +- [ ] Yes +- [ ] No + +### Is the program free of external packages except for the official AMQP client (github.com/rabbitmq/amqp091-go)? +- [ ] Yes +- [ ] No + +## Basic Functionality (Baseline) +### Does the program accept orders through REST API? +- [ ] Yes +- [ ] No + +### Does the program correctly connect to RabbitMQ server? +- [ ] Yes +- [ ] No + +### Does the program use RabbitMQ work queue pattern to distribute orders among cooks? +- [ ] Yes +- [ ] No + +### Does the program handle order data correctly (order ID, dishes list, customer information, order type)? +- [ ] Yes +- [ ] No + +### Does the program acknowledge orders only after successful processing? +- [ ] Yes +- [ ] No + +### Does the program enforce maximum 50 concurrent orders constraint? +- [ ] Yes +- [ ] No + +### Does each order get processed exactly once? +- [ ] Yes +- [ ] No -### Services are split into inbound/outbound ports and pure domain packages +## Multiple Workers Implementation +### Does the program support multiple cooks working simultaneously? - [ ] Yes - [ ] No -### `api-gateway`, `kitchen`, `delivery` are built as **three independent Go binaries** +### Does the program distribute orders evenly among available cooks using round-robin? - [ ] Yes - [ ] No -### No import cycles: upper layers never import lower ones +### Can new cooks connect dynamically without affecting the system? - [ ] Yes - [ ] No -### All third-party tech (`pgx`, `amqp091-go`) are wrapped in adapters +### Does the program enforce maximum 10 cooks simultaneously constraint? - [ ] Yes - [ ] No -## PostgreSQL & Transactional Outbox +### Does each cook have a unique ID? +- [ ] Yes +- [ ] No -### `init.sql` creates every table, enum `order_status`, indexes +### Are unacknowledged messages redirected to other cooks when a cook disconnects? - [ ] Yes - [ ] No -### Order insert **and** first outbox event happen in one SQL transaction +## Order Status Tracking +### Does the program implement order status tracking system? - [ ] Yes - [ ] No -### Unpublished events are selected with `FOR UPDATE SKIP LOCKED` +### Does the program use publish/subscribe pattern (fanout exchange) for notifications? - [ ] Yes - [ ] No -### `published_at` is set only after broker **ACK** +### Does the program track all required statuses (received, cooking, ready, delivered)? - [ ] Yes - [ ] No -### Inbox tables use `PRIMARY KEY(event_id)` + `ON CONFLICT DO NOTHING` +### Do notifications contain order_id, status, and timestamp? - [ ] Yes - [ ] No -## RabbitMQ – Topology, QoS, Retry, DLQ +### Does the program provide API endpoint for checking order status? +- [ ] Yes +- [ ] No + +### Can clients query the status of their orders? +- [ ] Yes +- [ ] No -### Exchanges / queues exactly match the declared topology +## Order Types and Routing +### Does the program support all three order types (dine-in, takeout, delivery)? - [ ] Yes - [ ] No -### Prefetch is applied via `basic.Qos(, 0, true)` +### Does the program use topic exchange with correct routing keys (orders.kitchen.{type})? - [ ] Yes - [ ] No -### NACK → retry → TTL → source-exchange path works as specified +### Does the program implement different cooking times for different order types? - [ ] Yes - [ ] No -### Separate retry exchange **or** routing-key namespace for commands vs events +### Does the program validate order type on creation? - [ ] Yes - [ ] No -### After max retries the message is routed to `orders.dlq.q` +### Do delivery orders require and validate address information? - [ ] Yes - [ ] No -### All publishes use **Publisher Confirms** +### Can cooks listen to specific order types only? - [ ] Yes - [ ] No -## HTTP API +## Priority Queue Implementation +### Does the program support order prioritization using RabbitMQ priority queues? +- [ ] Yes +- [ ] No -### `POST /orders` and `GET /orders/{id}` implemented +### Are queues configured with x-max-priority parameter? - [ ] Yes - [ ] No -### Error responses are JSON with `"error"` field +### Does the program implement correct priority levels (1-normal, 5-medium, 10-high)? - [ ] Yes - [ ] No -### Query param `force_payment_fail` accepted **only in DEV** +### Are VIP clients automatically assigned high priority? - [ ] Yes - [ ] No -### API never leaks sensitive data (PII) in payloads +### Are large orders (>$50) automatically assigned medium priority? - [ ] Yes - [ ] No -## Configuration / Deployment +### Can urgent orders be manually marked as priority? +- [ ] Yes +- [ ] No -### All settings come from **environment variables**; only `--help` flag exists +### Do cooks receive orders in priority order? - [ ] Yes - [ ] No -### `docker compose up` boots everything; app works end-to-end +## Service Modes Implementation +### Does the program implement order-service mode with REST API? - [ ] Yes - [ ] No -### Services handle `SIGINT`/`SIGTERM`, drain consumers and finish gracefully +### Does the program implement kitchen-worker mode? - [ ] Yes - [ ] No -### Startup errors exit with non-zero code and clear message +### Does the program implement tracking-service mode? - [ ] Yes - [ ] No -## Logging & Observability +### Does the program implement notification-subscriber mode? +- [ ] Yes +- [ ] No -### `log/slog` used with structured fields (`order_id`, `event`) +### Does the program support --setup-queues command for RabbitMQ initialization? - [ ] Yes - [ ] No -### Log levels: **Info** on publish / **Warn** on retry / **Error** on DLQ +## Configuration and Command Line Arguments +### Does the program support --help flag with comprehensive usage information? - [ ] Yes - [ ] No -### Prometheus/expvar metrics expose DLQ rate and publish latency +### Does the program support --rabbitmq connection parameter? - [ ] Yes - [ ] No -## Testing +### Does the program support --port parameter for order service? +- [ ] Yes +- [ ] No -### Unit tests cover pure business logic (no RabbitMQ / PG) +### Does the program support --worker-id parameter for kitchen workers? - [ ] Yes - [ ] No -### Contract tests validate JSON schema of events +### Does the program support --order-types parameter for selective processing? - [ ] Yes - [ ] No -### End-to-end tests (Testcontainers-go) cover all 5 required scenarios +### Does the program support --customer parameter for notification subscription? - [ ] Yes - [ ] No -### Retry TTL is overridden ≤ 100 ms; full `go test ./...` finishes < 30 s +## Error Handling and Reliability +### Does the program handle RabbitMQ connection failures gracefully? - [ ] Yes - [ ] No -## Code Quality +### Does the program implement proper error messages with clear reasons? +- [ ] Yes +- [ ] No -### Passes `gofumpt`; uses only stdlib + `amqp091-go` + `pgx` +### Does the program handle invalid JSON in API requests? - [ ] Yes - [ ] No -### No cyclic imports or unnecessary global state +### Does the program handle missing required fields in orders? - [ ] Yes - [ ] No -## Documentation +### Does the program handle network disconnections properly? +- [ ] Yes +- [ ] No -### README contains the provided **Mermaid** topology diagram +### Does the program implement proper cleanup on shutdown? - [ ] Yes - [ ] No -### Steps to reproduce locally are clear and complete +## API Endpoints +### Does the program implement POST /orders endpoint for order creation? - [ ] Yes - [ ] No +### Does the program implement GET /orders/{id}/status endpoint? +- [ ] Yes +- [ ] No + +### Do API responses follow the specified JSON format? +- [ ] Yes +- [ ] No + +### Does the program handle HTTP request validation properly? +- [ ] Yes +- [ ] No + +### Does the program return appropriate HTTP status codes? +- [ ] Yes +- [ ] No + +## RabbitMQ Integration +### Does the program properly declare and configure exchanges? +- [ ] Yes +- [ ] No + +### Does the program properly declare and configure queues? +- [ ] Yes +- [ ] No + +### Does the program implement proper message acknowledgment? +- [ ] Yes +- [ ] No + +### Does the program handle RabbitMQ channel closures? +- [ ] Yes +- [ ] No + +### Does the program implement proper connection recovery? +- [ ] Yes +- [ ] No + +## Concurrency and Performance +### Does the program handle concurrent order processing efficiently? +- [ ] Yes +- [ ] No + +### Does the program prevent race conditions in shared resources? +- [ ] Yes +- [ ] No + +### Does the program implement proper resource cleanup? +- [ ] Yes +- [ ] No + +### Does the program maintain performance under load? +- [ ] Yes +- [ ] No + +## Project Defense + +### Can the team explain their RabbitMQ exchange and queue design decisions? +- [ ] Yes +- [ ] No + +### Can the team explain how they implemented message routing patterns? +- [ ] Yes +- [ ] No + +### Can the team demonstrate understanding of work queue vs pub/sub patterns? +- [ ] Yes +- [ ] No + +### Can the team explain their error handling and reliability approach? +- [ ] Yes +- [ ] No + +### Can the team demonstrate the system working with multiple workers? +- [ ] Yes +- [ ] No + +### Can the team explain how they implemented priority queues? +- [ ] Yes +- [ ] No ## Detailed Feedback diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 5e61860..c6d229f 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -1,4 +1,4 @@ -# Restaurant Order Management System with RabbitMQ +# wheres-my-pizza ## Learning Objectives @@ -82,7 +82,7 @@ This approach gives us a clear path forward. By focusing on these key requiremen - Your code MUST be written in accordance with [gofumpt](https://github.com/mvdan/gofumpt). If not, you will automatically receive a score of `0`. - Your program MUST compile successfully. - Your program MUST NOT crash unexpectedly (any panics: `nil-pointer dereference`, `index out of range`, etc.). If this happens, you will receive `0` points during defense. -- Only built-in Go packages and the official AMQP client (`github.com/rabbitmq/amqp091-go`) are allowed. If other packages are used, you will receive a score of `0`. +- Only built-in Go packages, `pgx/v5` and the official AMQP client (`github.com/rabbitmq/amqp091-go`) are allowed. If other packages are used, you will receive a score of `0`. - RabbitMQ server MUST be running and available for connection. - The project MUST compile with the following command in the project root directory: From 37a2dd9b391abb85d94df20866ce409ce6e5b6e0 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Tue, 17 Jun 2025 10:40:59 -0500 Subject: [PATCH 07/20] fix: PR --- wheres-my-pizza/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index c6d229f..586e9b2 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -16,7 +16,7 @@ This project will teach you that before you start writing code, you should think ## Context -> you can’t polish your way out of bad architecture +> You can’t polish your way out of bad architecture. > The challenge we've chosen may seem unique, but at its core lies the common structure of many other distributed systems: data comes in, gets processed by various components, and is passed along the chain. @@ -31,7 +31,7 @@ A smart way to solve this type of problem is using message queue patterns. This **Work Queue Pattern** - One producer sends tasks to a queue -- Multiple consumers compete to receive tasks +- Multiple consumers wait for the task to arrive, but only one receives it. - Each task is processed by exactly one consumer - Provides load distribution among workers From d0609d2122692a59ae0fb6b112c99b6233636e90 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Wed, 18 Jun 2025 07:52:31 -0500 Subject: [PATCH 08/20] fix: PR --- wheres-my-pizza/README.md | 623 ++++++++++++++++++++++++++++---------- 1 file changed, 460 insertions(+), 163 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 586e9b2..37b2811 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -5,10 +5,11 @@ - Message Queue Systems - RabbitMQ Integration - Concurrent Programming +- Microservices Architecture ## Abstract -In this project, you will build a restaurant order management system using RabbitMQ as a message broker. The system simulates a real restaurant workflow where orders go through various processing stages: received -> cooking -> ready -> delivered. +In this project, you will build a restaurant order management system using RabbitMQ as a message broker and PostgreSQL for persistent storage. The system simulates a real restaurant workflow where orders go through various processing stages: received -> cooking -> ready -> delivered. Similar systems are used in real restaurants and food delivery services. For example, when you order food through an app, your order goes through an analogous processing system with task distribution among different staff members. @@ -16,7 +17,7 @@ This project will teach you that before you start writing code, you should think ## Context -> You can’t polish your way out of bad architecture. +> You can't polish your way out of bad architecture. > The challenge we've chosen may seem unique, but at its core lies the common structure of many other distributed systems: data comes in, gets processed by various components, and is passed along the chain. @@ -71,11 +72,198 @@ Let's call the combination of an order and its current state a "processing stage This approach gives us a clear path forward. By focusing on these key requirements, we can design a solution that is both elegant and efficient. +## System Architecture + +### Service Responsibilities + +**Order Service** +- Accept HTTP requests for new orders +- Validate order data and calculate totals +- Store orders in PostgreSQL +- Publish orders to RabbitMQ kitchen queue +- Provide order status API endpoints + +**Kitchen Workers** +- Consume orders from kitchen queue +- Simulate cooking process +- Update order status in database +- Publish status updates to notification exchange + +**Tracking Service** +- Monitor order status changes +- Handle status update requests +- Provide centralized status tracking +- Log all status transitions + +**Notification Subscriber** +- Listen for status update notifications +- Filter notifications by customer +- Display real-time status updates + +## Database Schema + +### Orders Table + +```sql +create table orders ( + "id" uuid primary key default gen_random_uuid(), + "created_at" timestamptz not null default now(), + "updated_at" timestamptz not null default now(), + "number" varchar(20) unique not null, + "customer_name" varchar(100) not null, + "customer_type" varchar(10) default 'regular', + "type" varchar(10) not null check (order_type in ('dine-in', 'takeout', 'delivery')), + "table_number" integer, + "delivery_address" text, + "total_amount" decimal(10,2) not null, + "priority" integer default 1, + "status" varchar(20) default 'received', + "processed_by" varchar(50), + "completed_at" timestamptz +); +``` + +### Order Items Table + +```sql +CREATE TABLE order_items ( + "id" uuid primary key default gen_random_uuid(), + "created_at" timestamptz not null default now(), + "order_id" varchar(20) references orders(id), + "name" varchar(100) not null, + "quantity" integer not null, + "price" decimal(8,2) not null +); +``` + +### Order Status Log Table + +```sql +create table order_status_log ( + "id" uuid primary key default gen_random_uuid(), + "created_at" timestamptz not null default now(), + order_id varchar(20) references orders(id), + "status" varchar(20), + "changed_by" varchar(50), + "changed_at" timestamp default current_timestamp, + "notes" text +); +``` + +### Workers Table + +```sql +create table workers ( + "id" uuid primary key default gen_random_uuid(), + "created_at" timestamptz not null default now(), + "name" varchar(50) unique not null, + "type" varchar(20) not null, + "status" varchar(10) default 'online', + "last_seen" timestamp default current_timestamp, + "orders_processed" integer default 0 +); +``` + +## RabbitMQ Configuration + +### Exchanges and Queues Setup + +``` +Exchanges: +├── orders_direct (type: direct, durable: true) +│ └── Routing Keys: +│ ├── kitchen.dine-in +│ ├── kitchen.takeout +│ └── kitchen.delivery +│ +└── notifications_fanout (type: fanout, durable: true) + └── Broadcasts to all subscribers + +Queues: +├── kitchen_queue (durable: true, x-max-priority: 10) +├── kitchen_dine_in_queue (durable: true, x-max-priority: 10) +├── kitchen_takeout_queue (durable: true, x-max-priority: 10) +├── kitchen_delivery_queue (durable: true, x-max-priority: 10) +└── notifications_queue (durable: true, auto-delete: false) +``` + +### Message Formats + +**Order Message** +```json +{ + "order_number": "ORD_20241216_001", + "customer_name": "John Doe", + "customer_type": "vip", + "order_type": "delivery", + "table_number": null, + "delivery_address": "123 Main St, City", + "items": [ + { + "name": "Margherita Pizza", + "quantity": 1, + "price": 15.99 + } + ], + "total_amount": 15.99, + "priority": 10 +} +``` + +**Status Update Message** +```json +{ + "order_number": "ORD_20241216_001", + "old_status": "received", + "new_status": "cooking", + "changed_by": "chef_mario", + "timestamp": "2024-12-16T10:32:00Z", + "estimated_completion": "2024-12-16T10:42:00Z" +} +``` + +## Logging Format + +All services must implement structured logging in JSON format: + +```json +{ + "timestamp": "2024-12-16T10:30:15.123Z", + "level": "INFO", + "service": "order-service", + "worker_name": "chef_mario", + "order_number": "ORD_20241216_001", + "action": "order_received", + "message": "Order received and queued for processing", + "duration_ms": 45, + "details": { + "customer_name": "John Doe", + "order_type": "delivery", + "priority": 10, + "total_amount": 15.99 + } +} +``` + +**Log Levels:** +- ERROR: System errors, connection failures, invalid data +- WARN: Retry attempts, temporary failures, configuration issues +- INFO: Order lifecycle events, worker status changes +- DEBUG: Detailed processing information, performance metrics + +**Required Log Events:** +- Order received/processed/completed +- Worker connected/disconnected +- Status changes +- Error conditions and recoveries +- Performance metrics (processing times) + ## Resources - [RabbitMQ Documentation](https://www.rabbitmq.com/documentation.html) - [RabbitMQ Docker Image](https://hub.docker.com/_/rabbitmq) - [Go AMQP Client](https://github.com/rabbitmq/amqp091-go) +- [PostgreSQL Go Driver (pgx)](https://github.com/jackc/pgx) ## General Criteria @@ -84,6 +272,10 @@ This approach gives us a clear path forward. By focusing on these key requiremen - Your program MUST NOT crash unexpectedly (any panics: `nil-pointer dereference`, `index out of range`, etc.). If this happens, you will receive `0` points during defense. - Only built-in Go packages, `pgx/v5` and the official AMQP client (`github.com/rabbitmq/amqp091-go`) are allowed. If other packages are used, you will receive a score of `0`. - RabbitMQ server MUST be running and available for connection. +- PostgreSQL database MUST be running and accessible for all services +- All RabbitMQ connections must handle reconnection scenarios +- Implement proper graceful shutdown for all services +- All database operations must be transactional where appropriate - The project MUST compile with the following command in the project root directory: ```sh @@ -96,299 +288,404 @@ $ go build -o restaurant-system . By default, your program should implement a basic order processing system using RabbitMQ work queue pattern to distribute orders among cooks. +#### Database Setup: + +Create all required tables as specified in the Database Schema section. Each service must connect to PostgreSQL and handle connection errors gracefully. + +#### RabbitMQ Setup: + +- Queue name: `kitchen_queue` +- Exchange: direct exchange named `orders_direct` +- Routing key: `kitchen.order` +- Queue should be durable and survive server restarts +- Messages should be persistent +- Connection must handle reconnection automatically + +#### Order Processing Flow: + +1. Order Service receives HTTP POST request +2. Validates request data and calculates totals +3. Stores order in PostgreSQL orders table +4. Publishes order message to RabbitMQ +5. Kitchen Worker consumes order from queue +6. Updates order status to 'cooking' in database +7. Simulates cooking process (configurable duration) +8. Updates order status to 'ready' in database +9. Logs completion and acknowledges message + Outcomes: - Program accepts orders through REST API -- Program uses RabbitMQ to store orders in queue -- Program distributes orders among available cooks -- Program handles basic error scenarios +- Program uses PostgreSQL to persist order data +- Program uses RabbitMQ to distribute orders among cooks +- Program handles database and RabbitMQ connection failures +- All status changes are logged to database Notes: -- Orders contain: order ID, list of dishes, customer information, order type -- Cooks compete for orders (competing consumers) -- Default cooking time 10 seconds for demonstration +- Orders contain: order number, customer info, items list, pricing, timestamps +- Cooks compete for orders (competing consumers pattern) +- Default cooking time: 10 seconds for demo purposes +- Order IDs follow format: `ORD_YYYYMMDD_NNN` Constraints: -- On any error, output error message with reason specified -- Orders must be acknowledged only after successful processing +- On any error, output structured JSON log with error details +- Orders must be acknowledged only after successful database update - Maximum 50 concurrent orders in system - Each order must be processed exactly once +- Database transactions must be used for order updates Examples: ```sh $ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management +$ docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:13 + +# Initialize database and queues +$ ./restaurant-system --setup-db --db="postgres://user:password@localhost/restaurant" +$ ./restaurant-system --setup-queues --rabbitmq="localhost:5672" # Start order service -$ ./restaurant-system --mode=order-service --port=3000 -Order service started on port 3000 -Connected to RabbitMQ at localhost:5672 -Kitchen queue initialized +$ ./restaurant-system --mode=order-service --port=3000 --db="postgres://user:password@localhost/restaurant" +{"timestamp":"2024-12-16T10:30:00Z","level":"INFO","service":"order-service","message":"Service started on port 3000"} + +# Start kitchen worker +$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --db="postgres://user:password@localhost/restaurant" +{"timestamp":"2024-12-16T10:30:05Z","level":"INFO","service":"kitchen-worker","worker_name":"chef_mario","message":"Worker connected and ready"} # Place order $ curl -X POST http://localhost:3000/orders \ -H "Content-Type: application/json" \ -d '{ "customer_name": "John Doe", - "phone": "555-0123", + "order_type": "takeout", "items": [ - {"name": "Margherita Pizza", "quantity": 1}, - {"name": "Caesar Salad", "quantity": 1} + {"name": "Margherita Pizza", "quantity": 1, "price": 15.99}, + {"name": "Caesar Salad", "quantity": 1, "price": 8.99} ] }' -Response: {"order_id": "ORD_001", "status": "received", "message": "Order placed successfully"} - -# Cook processes orders -$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_mario" -Kitchen worker chef_mario connected to RabbitMQ -Listening for orders on kitchen_queue... -Received order ORD_001: Margherita Pizza (1), Caesar Salad (1) -Customer: John Doe (555-0123) -Processing order... (10 seconds) -Order ORD_001 completed by chef_mario +Response: {"order_number": "ORD_20241216_001", "status": "received", "total_amount": 24.98} ``` ### Multiple Workers Your program should support multiple cooks working simultaneously with automatic order distribution among them. +#### Worker Management: + +- Register workers in PostgreSQL workers table +- Track worker status and last seen timestamp +- Handle worker disconnections gracefully +- Redistribute unacknowledged orders to available workers + +#### Load Balancing: + +- Use RabbitMQ round-robin distribution by default +- Track worker performance metrics in database +- Support worker-specific order type filtering +- Implement worker health monitoring + Outcomes: -- Program supports multiple cooks +- Program supports multiple cooks simultaneously - Orders are distributed evenly among available cooks -- New cooks can connect dynamically +- New cooks can connect dynamically without service restart - Cook disconnection doesn't affect processing of other orders +- Worker status is tracked in database Constraints: -- Use RabbitMQ round-robin distribution -- Maximum 10 cooks simultaneously -- Each cook must have unique ID -- Unacknowledged messages should be redirected to other cooks +- Use RabbitMQ round-robin distribution mechanism +- Maximum 10 active workers simultaneously +- Each cook must have unique worker name +- Unacknowledged messages are redelivered to other workers +- Worker heartbeat every 30 seconds ```sh -# Start multiple cooks -$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_mario" & -$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_luigi" & -$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_anna" & - -Kitchen worker chef_mario connected -Kitchen worker chef_luigi connected -Kitchen worker chef_anna connected -All workers listening on kitchen_queue - -# Monitor order distribution -$ curl -X POST http://localhost:3000/orders -d '{"customer_name":"Alice","items":[{"name":"Pizza","quantity":1}]}' -$ curl -X POST http://localhost:3000/orders -d '{"customer_name":"Bob","items":[{"name":"Pasta","quantity":1}]}' -$ curl -X POST http://localhost:3000/orders -d '{"customer_name":"Carol","items":[{"name":"Salad","quantity":1}]}' - -# In cook logs: -# chef_mario: Received order ORD_001 (Alice - Pizza) -# chef_luigi: Received order ORD_002 (Bob - Pasta) -# chef_anna: Received order ORD_003 (Carol - Salad) +# Start multiple workers with different specializations +$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --order-types="pizza,pasta" & +$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_luigi" --order-types="salad,soup" & +$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_anna" & + +# Worker logs show specialization +{"timestamp":"2024-12-16T10:30:00Z","level":"INFO","service":"kitchen-worker","worker_name":"chef_mario","message":"Worker registered for order types: pizza,pasta"} + +# Monitor worker status +$ curl http://localhost:3000/workers/status +[ + {"worker_name": "chef_mario", "status": "online", "orders_processed": 5, "last_seen": "2024-12-16T10:35:00Z"}, + {"worker_name": "chef_luigi", "status": "online", "orders_processed": 3, "last_seen": "2024-12-16T10:35:01Z"} +] ``` ### Order Status Tracking -Your program should implement an order status tracking system using publish/subscribe pattern for notifications. +Your program should implement a comprehensive order status tracking system using publish/subscribe pattern for real-time notifications. + +#### Status Management: + +- Track all status transitions in `order_status_log` table +- Implement status validation (prevent invalid transitions) +- Provide real-time status updates via RabbitMQ +- Support status queries via REST API + +#### Notification System: + +- Use fanout exchange for broadcasting status updates +- Support customer-specific notification filtering +- Log all notification deliveries +- Handle notification delivery failures Outcomes: -- Program tracks status of each order +- Program tracks complete status history for each order - Order status is updated at each processing stage -- Clients can query status of their orders -- System sends notifications about status changes +- Clients can query current and historical status +- System sends real-time notifications about status changes +- Status transitions are validated and logged Constraints: -- Statuses: received, cooking, ready, delivered -- Use fanout exchange for notifications -- Notifications must contain order_id, status, timestamp -- API endpoint for checking order status +- Valid statuses: received, cooking, ready, out-for-delivery, delivered, cancelled +- Use fanout exchange named `notifications_fanout` +- All status changes must be atomic (database + message) +- Notifications include timestamp, order details, estimated completion +- API endpoints for status queries and history ```sh # Start tracking service -$ ./restaurant-system --mode=tracking-service & - -# Subscribe to order notifications -$ ./restaurant-system --mode=notification-subscriber --customer="John Doe" -Subscribed to order notifications -Received: Order ORD_001 status changed to 'cooking' at 2024-12-16 10:30:15 -Received: Order ORD_001 status changed to 'ready' at 2024-12-16 10:40:20 - -# Check order status via API -$ curl http://localhost:3000/orders/ORD_001/status -{"order_id": "ORD_001", "status": "ready", "updated_at": "2024-12-16T10:40:20Z"} +$ ./restaurant-system --mode=tracking-service --db="postgres://user:password@localhost/restaurant" & +{"timestamp":"2024-12-16T10:30:00Z","level":"INFO","service":"tracking-service","message":"Tracking service started"} + +# Start notification subscriber +$ ./restaurant-system --mode=notification-subscriber --customer="John Doe" & +{"timestamp":"2024-12-16T10:30:05Z","level":"INFO","service":"notification-subscriber","message":"Subscribed to notifications for customer: John Doe"} + +# Check order status and history +$ curl http://localhost:3000/orders/ORD_20241216_001/status +{ + "order_number": "ORD_20241216_001", + "current_status": "cooking", + "updated_at": "2024-12-16T10:32:00Z", + "estimated_completion": "2024-12-16T10:42:00Z", + "processed_by": "chef_mario" +} + +$ curl http://localhost:3000/orders/ORD_20241216_001/history +[ + {"status": "received", "timestamp": "2024-12-16T10:30:00Z", "changed_by": "order-service"}, + {"status": "cooking", "timestamp": "2024-12-16T10:32:00Z", "changed_by": "chef_mario"} +] ``` ### Order Types and Routing -Your program should support different order types (dine-in, takeout, delivery) with routing based on order type. +Your program should support different order types with specialized routing and processing requirements. + +#### Order Type Processing: + +- **Dine-in**: Requires table number, shorter cooking time (8 sec) +- **Takeout**: Standard processing, medium cooking time (10 sec) +- **Delivery**: Requires address validation, longer cooking time (12 sec) + +#### Routing Configuration: + +- Use topic exchange with routing patterns +- Route orders to specialized worker queues +- Support worker subscription to specific order types +- Implement priority routing for rush orders Outcomes: -- Program supports three order types: dine-in, takeout, delivery -- Orders are routed to appropriate queues based on type -- Different order types have different processing times -- Delivery orders require additional processing +- Program supports three distinct order types with different requirements +- Orders are routed to appropriate processing queues +- Different order types have different processing times and validation +- Delivery orders include address validation and special handling +- Workers can specialize in specific order types Constraints: -- Use topic exchange with routing keys: `orders.kitchen.{type}` -- Cooking times: dine-in (8 sec), takeout (10 sec), delivery (12 sec) -- Delivery orders must include address -- Order type validation on creation +- Use topic exchange named `orders_topic` with routing keys `kitchen.{type}.{priority}` +- Order type validation on creation with specific field requirements +- Cooking time varies by type: dine-in (8s), takeout (10s), delivery (12s) +- Delivery orders require valid address format +- Table numbers required for dine-in orders ```sh -# Dine-in order -$ curl -X POST http://localhost:3000/orders \ - -H "Content-Type: application/json" \ - -d '{ - "customer_name": "John Doe", - "order_type": "dine-in", - "table_number": 5, - "items": [{"name": "Steak", "quantity": 1}] - }' +# Place different order types +$ curl -X POST http://localhost:3000/orders \ + -d '{"customer_name": "John", "order_type": "dine-in", "table_number": 5, + "items": [{"name": "Steak", "quantity": 1, "price": 25.99}]}' -# Takeout order $ curl -X POST http://localhost:3000/orders \ - -H "Content-Type: application/json" \ - -d '{ - "customer_name": "Jane Smith", - "order_type": "takeout", - "items": [{"name": "Burger", "quantity": 2}] - }' + -d '{"customer_name": "Jane", "order_type": "delivery", + "delivery_address": "123 Main St, City, State 12345", + "items": [{"name": "Pizza", "quantity": 1, "price": 18.99}]}' -# Delivery order -$ curl -X POST http://localhost:3000/orders \ - -H "Content-Type: application/json" \ - -d '{ - "customer_name": "Bob Wilson", - "order_type": "delivery", - "address": "123 Main St, City", - "items": [{"name": "Pizza", "quantity": 1}] - }' - -# Cook can listen to specific order types -$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_mario" --order-types="dine-in,takeout" -chef_mario listening for dine-in and takeout orders only +# Worker specializing in delivery orders +$ ./restaurant-system --mode=kitchen-worker --worker-name="delivery_chef" --order-types="delivery" +{"timestamp":"2024-12-16T10:30:00Z","level":"INFO","service":"kitchen-worker","worker_name":"delivery_chef","message":"Listening for delivery orders only"} ``` ### Priority Queue -Your program should support order prioritization using RabbitMQ priority queues. +Your program should support order prioritization using RabbitMQ priority queues with automatic priority assignment. + +#### Priority Assignment Rules: + +- **Priority 10 (High)**: VIP customers, rush orders, orders > $100 +- **Priority 5 (Medium)**: Large orders ($50-$100), repeat customers +- **Priority 1 (Normal)**: Standard orders + +#### Priority Processing: + +- Configure all queues with x-max-priority: 10 +- Workers process high-priority orders first +- Track priority metrics in database +- Support manual priority override for special cases Outcomes: -- Program supports priority orders -- VIP clients receive high priority automatically -- Large orders (>$50) receive medium priority -- Urgent orders can be marked as priority +- Program automatically assigns priorities based on order characteristics +- VIP customers and large orders receive expedited processing +- Workers process orders in priority order +- Priority assignment is logged and auditable +- System supports manual priority overrides Constraints: -- Priorities: 1 (normal), 5 (medium), 10 (high) -- Configure queues with x-max-priority parameter -- VIP status determined by client ID or flag -- Priority calculated when placing order +- All kitchen queues configured with x-max-priority parameter set to 10 +- Priority calculated automatically based on customer type and order value +- VIP status stored in customer database or determined by previous orders +- Priority changes logged in order_status_log table +- High-priority orders bypass normal queue position ```sh -# VIP order (high priority) +# VIP customer order (automatically high priority) $ curl -X POST http://localhost:3000/orders \ - -H "Content-Type: application/json" \ -d '{ "customer_name": "VIP Customer", - "customer_type": "VIP", - "items": [{"name": "Lobster", "quantity": 1, "price": 45.99}], + "customer_type": "vip", + "order_type": "delivery", + "delivery_address": "456 Executive Blvd", + "items": [{"name": "Premium Steak", "quantity": 1, "price": 45.99}], "rush": true }' -Response: {"order_id": "ORD_002", "priority": 10, "status": "received"} +Response: {"order_number": "ORD_20241216_002", "priority": 10, "status": "received", "estimated_completion": "2024-12-16T10:45:00Z"} -# Large order (medium priority) +# Large order (automatically medium priority) $ curl -X POST http://localhost:3000/orders \ - -H "Content-Type: application/json" \ -d '{ - "customer_name": "Regular Customer", + "customer_name": "Office Catering", "items": [ - {"name": "Pizza", "quantity": 3, "price": 15.99}, - {"name": "Salad", "quantity": 2, "price": 8.99} + {"name": "Pizza", "quantity": 5, "price": 15.99}, + {"name": "Salad", "quantity": 3, "price": 8.99} ] }' -Response: {"order_id": "ORD_003", "priority": 5, "status": "received"} +Response: {"order_number": "ORD_20241216_003", "priority": 5, "total_amount": 106.92} -# Cook receives orders by priority -$ ./restaurant-system --mode=kitchen-worker --worker-id="chef_mario" -Received HIGH PRIORITY order ORD_002 (VIP Customer - Lobster) -Received MEDIUM PRIORITY order ORD_003 (Regular Customer - Pizza x3, Salad x2) +# Worker processes by priority +{"timestamp":"2024-12-16T10:32:00Z","level":"INFO","service":"kitchen-worker","worker_name":"chef_mario","order_number":"ORD_20241216_002","message":"Processing HIGH PRIORITY order","priority":10} ``` ### Usage -Your program should output usage information. +Your program should output comprehensive usage information for all operational modes. Outcomes: -- Program outputs text describing usage of all modes and options +- Program outputs detailed usage information for all modes and options +- Includes examples for common usage scenarios +- Documents all configuration parameters +- Provides troubleshooting guidance ```sh $ ./restaurant-system --help -Restaurant Order Management System with RabbitMQ +Restaurant Order Management System with RabbitMQ and PostgreSQL Usage: - restaurant-system --mode=order-service [--port=N] [--rabbitmq=HOST:PORT] - restaurant-system --mode=kitchen-worker --worker-id=ID [--order-types=TYPES] [--rabbitmq=HOST:PORT] - restaurant-system --mode=tracking-service [--rabbitmq=HOST:PORT] - restaurant-system --mode=notification-subscriber [--customer=NAME] [--rabbitmq=HOST:PORT] - restaurant-system --setup-queues [--rabbitmq=HOST:PORT] + restaurant-system --mode=order-service [OPTIONS] + restaurant-system --mode=kitchen-worker --worker-name=worker_name [OPTIONS] + restaurant-system --mode=tracking-service [OPTIONS] + restaurant-system --mode=notification-subscriber [OPTIONS] + restaurant-system --setup-db [OPTIONS] + restaurant-system --setup-queues [OPTIONS] restaurant-system --help Service Modes: - order-service REST API for receiving orders - kitchen-worker Kitchen staff processing orders - tracking-service Order status tracking service - notification-subscriber Customer notification service + order-service REST API for receiving and managing orders + kitchen-worker Kitchen staff processing orders from queue + tracking-service Centralized order status tracking service + notification-subscriber Real-time customer notification service Connection Options: - --rabbitmq RabbitMQ server address (default: localhost:5672) - --rabbitmq-user RabbitMQ username (default: guest) - --rabbitmq-pass RabbitMQ password (default: guest) + --db PostgreSQL connection string (required for all modes) + Format: postgres://user:pass@host:port/dbname + --rabbitmq RabbitMQ server address (default: localhost:5672) + --rabbitmq-user RabbitMQ username (default: guest) + --rabbitmq-pass RabbitMQ password (default: guest) Order Service Options: - --port HTTP port for REST API (default: 3000) + --port HTTP port for REST API (default: 3000) + --max-concurrent Maximum concurrent orders (default: 50) Kitchen Worker Options: - --worker-id Unique worker identifier (required) - --order-types Comma-separated order types to process (default: all) + --worker-name Unique worker name (required) + --order-types Comma-separated order types to process (default: all) + Options: dine-in,takeout,delivery + --cooking-time Base cooking time in seconds (default: 10) Notification Options: - --customer Customer name for notifications + --customer Customer name filter for notifications + --notification-types Types of notifications to receive (default: all) + +Setup Commands: + --setup-db Initialize PostgreSQL database schema + --setup-queues Initialize RabbitMQ exchanges and queues + --migrate-db Run database migrations -Setup: - --setup-queues Initialize RabbitMQ exchanges and queues +Logging Options: + --log-level Log level: DEBUG, INFO, WARN, ERROR (default: INFO) + --log-file Log file path (default: stdout) Examples: - # Setup RabbitMQ - ./restaurant-system --setup-queues + # Setup environment + ./restaurant-system --setup-db --db="postgres://user:pass@localhost/restaurant" + ./restaurant-system --setup-queues --rabbitmq="localhost:5672" # Start order service - ./restaurant-system --mode=order-service --port=3000 + ./restaurant-system --mode=order-service --port=3000 --db="postgres://user:pass@localhost/restaurant" - # Start kitchen worker - ./restaurant-system --mode=kitchen-worker --worker-id=chef1 + # Start specialized kitchen worker + ./restaurant-system --mode=kitchen-worker --worker-name=pizza_chef --order-types=dine-in,takeout --db="postgres://user:pass@localhost/restaurant" - # Monitor notifications + # Monitor specific customer notifications ./restaurant-system --mode=notification-subscriber --customer="John Doe" + +Environment Variables: + DB_URL PostgreSQL connection string + RABBITMQ RabbitMQ server address + LOG_LEVEL Default log level + +Troubleshooting: + - Ensure PostgreSQL and RabbitMQ services are running + - Check connection strings and credentials + - Verify database schema is initialized + - Check RabbitMQ management interface at http://localhost:15672 ``` ## Support If you get stuck, test your code with the example inputs from the project. You should get the same results. If not, re-read the description again. Perhaps you missed something, or your code is incorrect. -Make sure the RabbitMQ server is running and accessible. Check the connection to RabbitMQ and proper configuration of queues and exchanges. +Make sure both PostgreSQL and RabbitMQ servers are running and accessible. Check the connection strings and verify proper configuration of database schema and message queues. -If you're still stuck, ask a friend for help or take a break and come back later. +Test each component individually before integrating them together. Use the provided SQL scripts to verify database schema and the RabbitMQ management interface to monitor queue status. + +If you're still stuck, review the logging output for error details, check service dependencies, and ensure all required environment variables are set correctly. ## Guidelines from Author Before diving into code, it's crucial to step back and think about your system architecture. This project illustrates a fundamental principle of good software design - your architectural choices often determine the clarity and efficiency of your code. -Start with questions: How will components interact? Which message exchange patterns best fit the task? What delivery guarantees are needed? Only after you've carefully thought through these questions should you proceed to API design and code writing. +Start with questions: How will components interact? Which message exchange patterns best fit the task? What delivery guarantees are needed? How will you handle failures and ensure data consistency? Only after you've carefully thought through these questions should you proceed to API design and code writing. This approach may seem like extra work initially, but it pays off. Well-chosen architecture can make your code simpler, more readable, and often more efficient. It's like choosing the right tools before starting work - with the right foundation, the rest of the work becomes much easier. +Pay special attention to the data flow between services. Design your database schema first, then define your message formats, and finally implement the service logic. This order ensures consistency and reduces the need for major refactoring later. + Remember that in programming, as in many other things, thoughtful preparation is the key to success. Spend time on proper architecture, and you'll find that the coding process becomes smoother and more enjoyable. Good system architecture is the foundation of clear, efficient code. It often simplifies programming more than clever algorithms can. Invest time in architectural design first. This approach usually leads to more maintainable and understandable programs, regardless of their size or complexity. From a7bdcdbe133a411a3a51aaefae047eca49417017 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Wed, 18 Jun 2025 08:05:46 -0500 Subject: [PATCH 09/20] fix: Questions.md --- wheres-my-pizza/QUESTIONS.md | 168 +++++++++++++++++------------------ 1 file changed, 81 insertions(+), 87 deletions(-) diff --git a/wheres-my-pizza/QUESTIONS.md b/wheres-my-pizza/QUESTIONS.md index 5a82cb3..88511c8 100644 --- a/wheres-my-pizza/QUESTIONS.md +++ b/wheres-my-pizza/QUESTIONS.md @@ -15,296 +15,290 @@ - [ ] Yes - [ ] No -### Is the program free of external packages except for the official AMQP client (github.com/rabbitmq/amqp091-go)? +### Is the program free of external packages except for pgx/v5 and github.com/rabbitmq/amqp091-go? - [ ] Yes - [ ] No -## Basic Functionality (Baseline) -### Does the program accept orders through REST API? +## Architecture +### Does the application have clearly separated service components (Order Service, Kitchen Workers, Tracking Service)? - [ ] Yes - [ ] No -### Does the program correctly connect to RabbitMQ server? +### Does the application implement proper message queue patterns using RabbitMQ? - [ ] Yes - [ ] No -### Does the program use RabbitMQ work queue pattern to distribute orders among cooks? +### Does the application implement proper database integration with PostgreSQL? - [ ] Yes - [ ] No -### Does the program handle order data correctly (order ID, dishes list, customer information, order type)? +### Are components properly decoupled with clear interfaces between services? - [ ] Yes - [ ] No -### Does the program acknowledge orders only after successful processing? +## Database Implementation +### Does the program correctly create and use the orders table with all required fields? - [ ] Yes - [ ] No -### Does the program enforce maximum 50 concurrent orders constraint? +### Does the program correctly create and use the order_items table? - [ ] Yes - [ ] No -### Does each order get processed exactly once? +### Does the program correctly create and use the order_status_log table? - [ ] Yes - [ ] No -## Multiple Workers Implementation -### Does the program support multiple cooks working simultaneously? +### Does the program correctly create and use the workers table? - [ ] Yes - [ ] No -### Does the program distribute orders evenly among available cooks using round-robin? +### Does the program handle database connection failures gracefully? - [ ] Yes - [ ] No -### Can new cooks connect dynamically without affecting the system? +## RabbitMQ Implementation +### Does the program correctly set up RabbitMQ exchanges (orders_direct, notifications_fanout)? - [ ] Yes - [ ] No -### Does the program enforce maximum 10 cooks simultaneously constraint? +### Does the program correctly set up and use kitchen_queue with proper durability? - [ ] Yes - [ ] No -### Does each cook have a unique ID? +### Does the program implement Work Queue pattern for order distribution? - [ ] Yes - [ ] No -### Are unacknowledged messages redirected to other cooks when a cook disconnects? +### Does the program implement Publish/Subscribe pattern for notifications? - [ ] Yes - [ ] No -## Order Status Tracking -### Does the program implement order status tracking system? +### Does the program handle RabbitMQ connection failures with automatic reconnection? - [ ] Yes - [ ] No -### Does the program use publish/subscribe pattern (fanout exchange) for notifications? +## Order Processing +### Does the program accept orders through REST API with proper validation? - [ ] Yes - [ ] No -### Does the program track all required statuses (received, cooking, ready, delivered)? +### Does the program correctly calculate order totals and store in database? - [ ] Yes - [ ] No -### Do notifications contain order_id, status, and timestamp? +### Does the program publish orders to RabbitMQ queues correctly? - [ ] Yes - [ ] No -### Does the program provide API endpoint for checking order status? +### Does the program process orders exactly once (no duplicates)? - [ ] Yes - [ ] No -### Can clients query the status of their orders? +## Kitchen Workers Implementation +### Does the program support multiple kitchen workers working simultaneously? - [ ] Yes - [ ] No -## Order Types and Routing -### Does the program support all three order types (dine-in, takeout, delivery)? +### Does the program implement round-robin distribution among workers? - [ ] Yes - [ ] No -### Does the program use topic exchange with correct routing keys (orders.kitchen.{type})? +### Does the program track worker status and registration in database? - [ ] Yes - [ ] No -### Does the program implement different cooking times for different order types? +### Does the program handle worker disconnections gracefully? - [ ] Yes - [ ] No -### Does the program validate order type on creation? +### Does the program simulate cooking process with configurable duration? - [ ] Yes - [ ] No -### Do delivery orders require and validate address information? +## Order Status Tracking +### Does the program track all status transitions in order_status_log table? - [ ] Yes - [ ] No -### Can cooks listen to specific order types only? +### Does the program validate status transitions (prevent invalid changes)? - [ ] Yes - [ ] No -## Priority Queue Implementation -### Does the program support order prioritization using RabbitMQ priority queues? +### Does the program provide real-time status updates via RabbitMQ? - [ ] Yes - [ ] No -### Are queues configured with x-max-priority parameter? +### Does the program support status queries via REST API? - [ ] Yes - [ ] No -### Does the program implement correct priority levels (1-normal, 5-medium, 10-high)? +### Does the program implement fanout exchange for broadcasting notifications? - [ ] Yes - [ ] No -### Are VIP clients automatically assigned high priority? +## Order Types and Routing +### Does the program support dine-in orders with table number validation? - [ ] Yes - [ ] No -### Are large orders (>$50) automatically assigned medium priority? +### Does the program support takeout orders with standard processing? - [ ] Yes - [ ] No -### Can urgent orders be manually marked as priority? +### Does the program support delivery orders with address validation? - [ ] Yes - [ ] No -### Do cooks receive orders in priority order? +### Does the program implement different cooking times by order type? - [ ] Yes - [ ] No -## Service Modes Implementation -### Does the program implement order-service mode with REST API? +### Does the program route orders to specialized worker queues? - [ ] Yes - [ ] No -### Does the program implement kitchen-worker mode? +## Priority Queue Implementation +### Does the program configure RabbitMQ queues with x-max-priority parameter? - [ ] Yes - [ ] No -### Does the program implement tracking-service mode? +### Does the program automatically assign priorities based on customer type and order value? - [ ] Yes - [ ] No -### Does the program implement notification-subscriber mode? +### Does the program process VIP customers with high priority (10)? - [ ] Yes - [ ] No -### Does the program support --setup-queues command for RabbitMQ initialization? +### Does the program process large orders with medium priority (5)? - [ ] Yes - [ ] No -## Configuration and Command Line Arguments -### Does the program support --help flag with comprehensive usage information? +### Does the program log priority assignments in database? - [ ] Yes - [ ] No -### Does the program support --rabbitmq connection parameter? +## API Implementation +### Does the program implement order creation endpoint (POST /orders)? - [ ] Yes - [ ] No -### Does the program support --port parameter for order service? +### Does the program implement order status endpoint (GET /orders/{id}/status)? - [ ] Yes - [ ] No -### Does the program support --worker-id parameter for kitchen workers? +### Does the program implement order history endpoint (GET /orders/{id}/history)? - [ ] Yes - [ ] No -### Does the program support --order-types parameter for selective processing? +### Does the program implement worker status endpoint (GET /workers/status)? - [ ] Yes - [ ] No -### Does the program support --customer parameter for notification subscription? +### Does the program return proper HTTP status codes and JSON responses? - [ ] Yes - [ ] No -## Error Handling and Reliability -### Does the program handle RabbitMQ connection failures gracefully? +## Message Formats and Logging +### Does the program use proper JSON message format for orders? - [ ] Yes - [ ] No -### Does the program implement proper error messages with clear reasons? +### Does the program use proper JSON message format for status updates? - [ ] Yes - [ ] No -### Does the program handle invalid JSON in API requests? +### Does the program implement structured JSON logging? - [ ] Yes - [ ] No -### Does the program handle missing required fields in orders? +### Do logs include all required fields (timestamp, level, service, worker_name, etc.)? - [ ] Yes - [ ] No -### Does the program handle network disconnections properly? +### Does the program log all significant events (order lifecycle, worker status, errors)? - [ ] Yes - [ ] No -### Does the program implement proper cleanup on shutdown? +## Configuration and Setup +### Does the program support database setup command (--setup-db)? - [ ] Yes - [ ] No -## API Endpoints -### Does the program implement POST /orders endpoint for order creation? +### Does the program support RabbitMQ setup command (--setup-queues)? - [ ] Yes - [ ] No -### Does the program implement GET /orders/{id}/status endpoint? +### Does the program read database connection string from parameters? - [ ] Yes - [ ] No -### Do API responses follow the specified JSON format? +### Does the program read RabbitMQ connection details from parameters? - [ ] Yes - [ ] No -### Does the program handle HTTP request validation properly? +### Does the program support environment variables for configuration? - [ ] Yes - [ ] No -### Does the program return appropriate HTTP status codes? +## System Operation +### Does the program implement graceful shutdown handling for all services? - [ ] Yes - [ ] No -## RabbitMQ Integration -### Does the program properly declare and configure exchanges? +### Does the program display comprehensive usage information with --help flag? - [ ] Yes - [ ] No -### Does the program properly declare and configure queues? +### Does the program handle SIGINT and SIGTERM signals properly? - [ ] Yes - [ ] No -### Does the program implement proper message acknowledgment? +### Does the program support all operational modes (order-service, kitchen-worker, etc.)? - [ ] Yes - [ ] No -### Does the program handle RabbitMQ channel closures? +### Does the program validate worker names uniqueness? - [ ] Yes - [ ] No -### Does the program implement proper connection recovery? +## Concurrency and Reliability +### Does the program handle concurrent order processing safely? - [ ] Yes - [ ] No -## Concurrency and Performance -### Does the program handle concurrent order processing efficiently? +### Does the program use database transactions for order updates? - [ ] Yes - [ ] No -### Does the program prevent race conditions in shared resources? +### Does the program acknowledge RabbitMQ messages only after successful processing? - [ ] Yes - [ ] No -### Does the program implement proper resource cleanup? +### Does the program handle maximum concurrent orders limit (50)? - [ ] Yes - [ ] No -### Does the program maintain performance under load? +### Does the program implement proper error recovery mechanisms? - [ ] Yes - [ ] No ## Project Defense -### Can the team explain their RabbitMQ exchange and queue design decisions? -- [ ] Yes -- [ ] No - -### Can the team explain how they implemented message routing patterns? -- [ ] Yes -- [ ] No - -### Can the team demonstrate understanding of work queue vs pub/sub patterns? +### Can the team explain their microservices architecture decisions? - [ ] Yes - [ ] No -### Can the team explain their error handling and reliability approach? +### Can the team explain how they implemented message queue patterns? - [ ] Yes - [ ] No -### Can the team demonstrate the system working with multiple workers? +### Can the team demonstrate understanding of RabbitMQ features used? - [ ] Yes - [ ] No -### Can the team explain how they implemented priority queues? +### Can the team explain their database schema design and relationships? - [ ] Yes - [ ] No From c1657a672cd62bb92120380e20ae0a52b212cc87 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Thu, 19 Jun 2025 00:35:34 -0500 Subject: [PATCH 10/20] fix: PR --- wheres-my-pizza/README.md | 208 ++++++++++++++++++++++++++++++-------- 1 file changed, 165 insertions(+), 43 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 37b2811..1ad136f 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -103,62 +103,70 @@ This approach gives us a clear path forward. By focusing on these key requiremen ## Database Schema ### Orders Table +**Purpose**: Primary storage for all restaurant orders with complete order information +**Used by**: Order Service (insert), Kitchen Workers (status updates), Tracking Service (queries) ```sql create table orders ( "id" uuid primary key default gen_random_uuid(), "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), - "number" varchar(20) unique not null, - "customer_name" varchar(100) not null, - "customer_type" varchar(10) default 'regular', - "type" varchar(10) not null check (order_type in ('dine-in', 'takeout', 'delivery')), + "number" text unique not null, + "customer_name" text not null, + "customer_type" text default 'regular', + "type" text not null check (type in ('dine-in', 'takeout', 'delivery')), "table_number" integer, "delivery_address" text, "total_amount" decimal(10,2) not null, "priority" integer default 1, - "status" varchar(20) default 'received', - "processed_by" varchar(50), + "status" text default 'received', + "processed_by" text, "completed_at" timestamptz ); ``` ### Order Items Table +**Purpose**: Stores individual items within each order for detailed order composition +**Used by**: Order Service (insert items when creating orders), API queries for order details ```sql CREATE TABLE order_items ( "id" uuid primary key default gen_random_uuid(), "created_at" timestamptz not null default now(), - "order_id" varchar(20) references orders(id), - "name" varchar(100) not null, + "order_id" uuid references orders(id), + "name" text not null, "quantity" integer not null, "price" decimal(8,2) not null ); ``` ### Order Status Log Table +**Purpose**: Audit trail for all status changes throughout order lifecycle +**Used by**: All services (insert status changes), Tracking Service (status history queries) ```sql create table order_status_log ( "id" uuid primary key default gen_random_uuid(), "created_at" timestamptz not null default now(), - order_id varchar(20) references orders(id), - "status" varchar(20), - "changed_by" varchar(50), + order_id uuid references orders(id), + "status" text, + "changed_by" text, "changed_at" timestamp default current_timestamp, "notes" text ); ``` ### Workers Table +**Purpose**: Registry and monitoring of all kitchen workers and their current status +**Used by**: Kitchen Workers (registration and heartbeat), Tracking Service (worker monitoring) ```sql create table workers ( "id" uuid primary key default gen_random_uuid(), "created_at" timestamptz not null default now(), - "name" varchar(50) unique not null, - "type" varchar(20) not null, - "status" varchar(10) default 'online', + "name" text unique not null, + "type" text not null, + "status" text default 'online', "last_seen" timestamp default current_timestamp, "orders_processed" integer default 0 ); @@ -168,6 +176,12 @@ create table workers ( ### Exchanges and Queues Setup +**Exchange Types Explained:** + +**Direct Exchange (`orders_direct`)**: Routes messages to queues based on exact routing key match. Used for targeted order routing where each order type goes to its specific queue. Only workers listening to that exact routing key will receive the message. + +**Fanout Exchange (`notifications_fanout`)**: Broadcasts all messages to every queue bound to it, ignoring routing keys. Used for notifications where all subscribers need to receive status updates regardless of their specific interests. + ``` Exchanges: ├── orders_direct (type: direct, durable: true) @@ -181,15 +195,25 @@ Exchanges: Queues: ├── kitchen_queue (durable: true, x-max-priority: 10) +│ └── Read by: General kitchen workers ├── kitchen_dine_in_queue (durable: true, x-max-priority: 10) +│ └── Read by: Dine-in specialized workers ├── kitchen_takeout_queue (durable: true, x-max-priority: 10) +│ └── Read by: Takeout specialized workers ├── kitchen_delivery_queue (durable: true, x-max-priority: 10) +│ └── Read by: Delivery specialized workers └── notifications_queue (durable: true, auto-delete: false) + └── Read by: Notification subscribers, customer apps ``` ### Message Formats -**Order Message** +#### Order Message +**Purpose**: Contains complete order information for kitchen processing +**Sent to**: Kitchen queues (kitchen_queue, kitchen_dine_in_queue, etc.) +**Read by**: Kitchen Workers +**Routing**: Through orders_direct exchange using routing keys like "kitchen.delivery" + ```json { "order_number": "ORD_20241216_001", @@ -210,7 +234,12 @@ Queues: } ``` -**Status Update Message** +#### Status Update Message +**Purpose**: Notifies all interested parties about order status changes +**Sent to**: notifications_fanout exchange +**Read by**: Notification Subscribers, terminal +**Routing**: Broadcast to all queues bound to notifications_fanout exchange + ```json { "order_number": "ORD_20241216_001", @@ -245,18 +274,82 @@ All services must implement structured logging in JSON format: } ``` -**Log Levels:** -- ERROR: System errors, connection failures, invalid data -- WARN: Retry attempts, temporary failures, configuration issues -- INFO: Order lifecycle events, worker status changes -- DEBUG: Detailed processing information, performance metrics +### Log Levels Usage: + +**ERROR**: +- Database connection failures +- RabbitMQ connection drops +- Order validation failures (invalid data, missing required fields) +- Message publish/consume errors +- System crashes or unrecoverable errors + +**WARN**: +- Message retry attempts (when worker is temporarily unavailable) +- Database query timeout warnings +- Configuration issues (missing optional parameters, using defaults) +- Order processing delays (cooking time exceeded) +- Worker disconnection/reconnection events + +**INFO**: +- Order lifecycle events (received, cooking started, completed) +- Worker status changes (connected, disconnected, processing order) +- Service startup/shutdown +- Normal business operations +- Performance milestones (orders per hour, average processing time) + +**DEBUG**: +- Detailed message content (full order JSON) +- Database query execution details +- Internal processing steps +- Performance metrics (exact processing times, queue lengths) +- Development and troubleshooting information + +### Required Log Events: +- **Order received/processed/completed** (INFO level) +- **Worker connected/disconnected** (INFO level) +- **Status changes** (INFO level) +- **Error conditions and recoveries** (ERROR/WARN level) +- **Performance metrics** (INFO/DEBUG level) + +## Configuration Management + +### Database Configuration +Configuration is stored in environment variables and command-line flags. The system creates a configuration file at startup: + +```yaml +# config/database.yaml (auto-generated during --setup-db) +database: + host: localhost + port: 5432 + user: restaurant_user + password: restaurant_pass + database: restaurant_db +``` -**Required Log Events:** -- Order received/processed/completed -- Worker connected/disconnected -- Status changes -- Error conditions and recoveries -- Performance metrics (processing times) +### RabbitMQ Configuration +```yaml +# config/rabbitmq.yaml (auto-generated during --setup-queues) +rabbitmq: + host: localhost + port: 5672 + user: guest + password: guest + vhost: / + exchanges: + orders_direct: + type: direct + durable: true + notifications_fanout: + type: fanout + durable: true + queues: + kitchen_queue: + durable: true + max_priority: 10 + notifications_queue: + durable: true + auto_delete: false +``` ## Resources @@ -301,17 +394,45 @@ Create all required tables as specified in the Database Schema section. Each ser - Messages should be persistent - Connection must handle reconnection automatically -#### Order Processing Flow: - -1. Order Service receives HTTP POST request -2. Validates request data and calculates totals -3. Stores order in PostgreSQL orders table -4. Publishes order message to RabbitMQ -5. Kitchen Worker consumes order from queue -6. Updates order status to 'cooking' in database -7. Simulates cooking process (configurable duration) -8. Updates order status to 'ready' in database -9. Logs completion and acknowledges message +#### Orders: + +1. **Order Reception**: Order Service receives HTTP POST request with order data + - **Validation Rules**: + - customer_name: required, 1-100 characters, no special characters except spaces, hyphens, apostrophes + - order_type: required, must be one of: 'dine-in', 'takeout', 'delivery' + - items: required array, minimum 1 item, maximum 20 items per order + - item.name: required, 1-50 characters + - item.quantity: required, integer, 1-10 per item + - item.price: required, decimal, 0.01-999.99 + - table_number: required for dine-in orders, 1-100 + - delivery_address: required for delivery orders, minimum 10 characters + +2. **Order Calculation**: Calculate total amount and assign priority + - **Total Calculation**: Sum of (item.price * item.quantity) for all items + - **Priority Assignment**: + - VIP customers (customer_type="vip"): priority 10 + - Orders > $100: priority 5 + - Default: priority 1 + +3. **Database Storage**: Store order in PostgreSQL orders table within transaction + - Generate order number: `ORD_YYYYMMDD_NNN` (NNN = daily sequence number) + - Insert order record with status 'received' + - Log initial status in order_status_log + +4. **Message Publishing**: Publish order message to RabbitMQ + - Route to kitchen_queue using routing key 'kitchen.order' + - Message includes complete order data (see Order Message format) + +5. **Kitchen Processing**: Kitchen Worker consumes order from queue + - Acknowledge receipt and update status to 'cooking' + - Record processing start time and worker name + - Simulate cooking process (configurable duration) + +6. **Order Completion**: Update order status to 'ready' + - Record completion timestamp + - Update orders_processed counter for worker + - Log completion details + - Acknowledge message to RabbitMQ Outcomes: - Program accepts orders through REST API @@ -339,16 +460,16 @@ Examples: $ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management $ docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:13 -# Initialize database and queues +# Initialize database and queues (creates config files) $ ./restaurant-system --setup-db --db="postgres://user:password@localhost/restaurant" $ ./restaurant-system --setup-queues --rabbitmq="localhost:5672" -# Start order service -$ ./restaurant-system --mode=order-service --port=3000 --db="postgres://user:password@localhost/restaurant" +# Start order service (uses config files if flags not provided) +./restaurant-system --mode=order-service --port=3000 {"timestamp":"2024-12-16T10:30:00Z","level":"INFO","service":"order-service","message":"Service started on port 3000"} -# Start kitchen worker -$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --db="postgres://user:password@localhost/restaurant" +# Start kitchen worker (uses config files if flags not provided) +./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --order-types="dine-in,takeout" {"timestamp":"2024-12-16T10:30:05Z","level":"INFO","service":"kitchen-worker","worker_name":"chef_mario","message":"Worker connected and ready"} # Place order @@ -611,6 +732,7 @@ Service Modes: notification-subscriber Real-time customer notification service Connection Options: + # If not provided, reads from config.yaml --db PostgreSQL connection string (required for all modes) Format: postgres://user:pass@host:port/dbname --rabbitmq RabbitMQ server address (default: localhost:5672) From 08a25a2616a4fcd443dffd2d84edc7a2881ffd1a Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Thu, 19 Jun 2025 02:19:21 -0500 Subject: [PATCH 11/20] fix: questions --- wheres-my-pizza/QUESTIONS.md | 193 +++++++++++++++++++++-------------- 1 file changed, 119 insertions(+), 74 deletions(-) diff --git a/wheres-my-pizza/QUESTIONS.md b/wheres-my-pizza/QUESTIONS.md index 88511c8..544799c 100644 --- a/wheres-my-pizza/QUESTIONS.md +++ b/wheres-my-pizza/QUESTIONS.md @@ -15,230 +15,246 @@ - [ ] Yes - [ ] No -### Is the program free of external packages except for pgx/v5 and github.com/rabbitmq/amqp091-go? +### Is the program free of external packages except for pgx/v5 and official AMQP client? - [ ] Yes - [ ] No ## Architecture -### Does the application have clearly separated service components (Order Service, Kitchen Workers, Tracking Service)? +### Is the application structured according to microservices architecture principles? - [ ] Yes - [ ] No -### Does the application implement proper message queue patterns using RabbitMQ? +### Does the application have clearly separated service responsibilities (Order Service, Kitchen Workers, Tracking Service, Notification Subscriber)? - [ ] Yes - [ ] No -### Does the application implement proper database integration with PostgreSQL? +### Does the application implement proper message queue patterns (Work Queue, Publish/Subscribe, Routing)? - [ ] Yes - [ ] No -### Are components properly decoupled with clear interfaces between services? +### Are components properly decoupled through RabbitMQ message broker? - [ ] Yes - [ ] No -## Database Implementation -### Does the program correctly create and use the orders table with all required fields? +### Does the system follow proper separation of concerns between services? - [ ] Yes - [ ] No -### Does the program correctly create and use the order_items table? +## Database Setup and Schema +### Does the program correctly create all required database tables (orders, order_items, order_status_log, workers)? - [ ] Yes - [ ] No -### Does the program correctly create and use the order_status_log table? +### Does the orders table contain all required fields with proper constraints? - [ ] Yes - [ ] No -### Does the program correctly create and use the workers table? +### Does the order_items table properly reference orders with foreign key? - [ ] Yes - [ ] No -### Does the program handle database connection failures gracefully? +### Does the order_status_log table track all status changes with timestamps? - [ ] Yes - [ ] No -## RabbitMQ Implementation -### Does the program correctly set up RabbitMQ exchanges (orders_direct, notifications_fanout)? +### Does the workers table manage worker registration and monitoring? - [ ] Yes - [ ] No -### Does the program correctly set up and use kitchen_queue with proper durability? +## RabbitMQ Configuration +### Does the program correctly set up required exchanges (orders_direct, notifications_fanout)? - [ ] Yes - [ ] No -### Does the program implement Work Queue pattern for order distribution? +### Does the program create all required queues with proper durability settings? - [ ] Yes - [ ] No -### Does the program implement Publish/Subscribe pattern for notifications? +### Does the program configure priority queues with x-max-priority parameter? - [ ] Yes - [ ] No -### Does the program handle RabbitMQ connection failures with automatic reconnection? +### Does the program implement proper routing keys for different order types? +- [ ] Yes +- [ ] No + +### Does the program handle RabbitMQ connection failures and reconnection? - [ ] Yes - [ ] No ## Order Processing -### Does the program accept orders through REST API with proper validation? +### Does the Order Service accept HTTP POST requests for new orders? - [ ] Yes - [ ] No -### Does the program correctly calculate order totals and store in database? +### Does the program validate order data according to specified rules? - [ ] Yes - [ ] No -### Does the program publish orders to RabbitMQ queues correctly? +### Does the program calculate total amounts and assign priorities correctly? - [ ] Yes - [ ] No -### Does the program process orders exactly once (no duplicates)? +### Does the program generate proper order numbers in ORD_YYYYMMDD_NNN format? - [ ] Yes - [ ] No -## Kitchen Workers Implementation -### Does the program support multiple kitchen workers working simultaneously? +### Does the program store orders in PostgreSQL within transactions? - [ ] Yes - [ ] No -### Does the program implement round-robin distribution among workers? +### Does the program publish order messages to RabbitMQ kitchen queue? - [ ] Yes - [ ] No -### Does the program track worker status and registration in database? +## Kitchen Worker Implementation +### Do Kitchen Workers consume orders from the correct queues? - [ ] Yes - [ ] No -### Does the program handle worker disconnections gracefully? +### Do Kitchen Workers update order status to 'cooking' when processing starts? - [ ] Yes - [ ] No -### Does the program simulate cooking process with configurable duration? +### Do Kitchen Workers simulate cooking process with configurable duration? - [ ] Yes - [ ] No -## Order Status Tracking -### Does the program track all status transitions in order_status_log table? +### Do Kitchen Workers update order status to 'ready' upon completion? - [ ] Yes - [ ] No -### Does the program validate status transitions (prevent invalid changes)? +### Do Kitchen Workers acknowledge messages only after successful database updates? - [ ] Yes - [ ] No -### Does the program provide real-time status updates via RabbitMQ? +## Multiple Workers Support +### Does the program support multiple kitchen workers simultaneously? - [ ] Yes - [ ] No -### Does the program support status queries via REST API? +### Does the program distribute orders evenly among available workers using round-robin? - [ ] Yes - [ ] No -### Does the program implement fanout exchange for broadcasting notifications? +### Does the program register workers in the PostgreSQL workers table? - [ ] Yes - [ ] No -## Order Types and Routing -### Does the program support dine-in orders with table number validation? +### Does the program track worker status and performance metrics? - [ ] Yes - [ ] No -### Does the program support takeout orders with standard processing? +### Does the program handle worker disconnections gracefully? - [ ] Yes - [ ] No -### Does the program support delivery orders with address validation? +## Order Status Tracking +### Does the program track all status transitions in order_status_log table? - [ ] Yes - [ ] No -### Does the program implement different cooking times by order type? +### Does the program validate status transitions to prevent invalid changes? - [ ] Yes - [ ] No -### Does the program route orders to specialized worker queues? +### Does the program provide REST API endpoints for status queries? - [ ] Yes - [ ] No -## Priority Queue Implementation -### Does the program configure RabbitMQ queues with x-max-priority parameter? +### Does the program provide order history through API endpoints? - [ ] Yes - [ ] No -### Does the program automatically assign priorities based on customer type and order value? +### Does the program implement atomic status updates (database + message)? - [ ] Yes - [ ] No -### Does the program process VIP customers with high priority (10)? +## Notification System +### Does the program use fanout exchange for broadcasting status updates? - [ ] Yes - [ ] No -### Does the program process large orders with medium priority (5)? +### Does the program send real-time notifications about status changes? - [ ] Yes - [ ] No -### Does the program log priority assignments in database? +### Does the program support customer-specific notification filtering? - [ ] Yes - [ ] No -## API Implementation -### Does the program implement order creation endpoint (POST /orders)? +### Does the program log all notification deliveries? - [ ] Yes - [ ] No -### Does the program implement order status endpoint (GET /orders/{id}/status)? +### Does the program handle notification delivery failures gracefully? - [ ] Yes - [ ] No -### Does the program implement order history endpoint (GET /orders/{id}/history)? +## Order Types and Routing +### Does the program support three distinct order types (dine-in, takeout, delivery)? +- [ ] Yes +- [ ] No + +### Does the program enforce different validation rules for each order type? +- [ ] Yes +- [ ] No + +### Does the program implement different cooking times for different order types? +- [ ] Yes +- [ ] No + +### Does the program route orders to specialized worker queues based on type? - [ ] Yes - [ ] No -### Does the program implement worker status endpoint (GET /workers/status)? +### Does the program validate table numbers for dine-in and addresses for delivery orders? - [ ] Yes - [ ] No -### Does the program return proper HTTP status codes and JSON responses? +## Priority Queue Implementation +### Does the program automatically assign priorities based on order characteristics? - [ ] Yes - [ ] No -## Message Formats and Logging -### Does the program use proper JSON message format for orders? +### Does the program give VIP customers priority 10 (high priority)? - [ ] Yes - [ ] No -### Does the program use proper JSON message format for status updates? +### Does the program assign priority 5 for large orders ($50-$100)? - [ ] Yes - [ ] No -### Does the program implement structured JSON logging? +### Do workers process orders in priority order (high priority first)? - [ ] Yes - [ ] No -### Do logs include all required fields (timestamp, level, service, worker_name, etc.)? +### Does the program log priority assignments in order_status_log? - [ ] Yes - [ ] No -### Does the program log all significant events (order lifecycle, worker status, errors)? +## Configuration and Logging +### Does the program properly read configuration from files and environment variables? - [ ] Yes - [ ] No -## Configuration and Setup -### Does the program support database setup command (--setup-db)? +### Does the configuration include PostgreSQL connection details? - [ ] Yes - [ ] No -### Does the program support RabbitMQ setup command (--setup-queues)? +### Does the configuration include RabbitMQ connection details? - [ ] Yes - [ ] No -### Does the program read database connection string from parameters? +### Does the program use structured JSON logging throughout the application? - [ ] Yes - [ ] No -### Does the program read RabbitMQ connection details from parameters? +### Do logs include contextual information like timestamps, service names, and order numbers? - [ ] Yes - [ ] No -### Does the program support environment variables for configuration? +### Does the program log all required events (order lifecycle, worker status, errors)? - [ ] Yes - [ ] No @@ -247,40 +263,61 @@ - [ ] Yes - [ ] No -### Does the program display comprehensive usage information with --help flag? +### Does the program display comprehensive usage information with `--help` flag? - [ ] Yes - [ ] No -### Does the program handle SIGINT and SIGTERM signals properly? +### Does the program handle database setup with `--setup-db` command? - [ ] Yes - [ ] No -### Does the program support all operational modes (order-service, kitchen-worker, etc.)? +### Does the program handle RabbitMQ setup with `--setup-queues` command? - [ ] Yes - [ ] No -### Does the program validate worker names uniqueness? +### Does the program support all required operational modes? - [ ] Yes - [ ] No -## Concurrency and Reliability -### Does the program handle concurrent order processing safely? +## API Implementation +### Does the program implement order creation endpoint (POST /orders)? +- [ ] Yes +- [ ] No + +### Does the program implement order status query endpoint (GET /orders/{id}/status)? +- [ ] Yes +- [ ] No + +### Does the program implement order history endpoint (GET /orders/{id}/history)? +- [ ] Yes +- [ ] No + +### Does the program implement worker status monitoring endpoint (GET /workers/status)? - [ ] Yes - [ ] No -### Does the program use database transactions for order updates? +### Do all API endpoints return proper HTTP status codes and error messages? - [ ] Yes - [ ] No -### Does the program acknowledge RabbitMQ messages only after successful processing? +## Message Handling +### Does the program properly format order messages according to specification? - [ ] Yes - [ ] No -### Does the program handle maximum concurrent orders limit (50)? +### Does the program properly format status update messages according to specification? - [ ] Yes - [ ] No -### Does the program implement proper error recovery mechanisms? +### Does the program ensure message persistence and durability? +- [ ] Yes +- [ ] No + +### Does the program handle message acknowledgments correctly? +- [ ] Yes +- [ ] No + +### Does the program implement proper error handling for failed message deliveries? - [ ] Yes - [ ] No @@ -298,7 +335,15 @@ - [ ] Yes - [ ] No -### Can the team explain their database schema design and relationships? +### Can the team explain their database transaction handling approach? +- [ ] Yes +- [ ] No + +### Can the team explain their priority queue implementation? +- [ ] Yes +- [ ] No + +### Can the team demonstrate the system working with multiple workers? - [ ] Yes - [ ] No From 75ed9fc49eceab07fa0f2950683ff541ee5ef577 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Fri, 27 Jun 2025 15:41:42 +0500 Subject: [PATCH 12/20] Update Context --- wheres-my-pizza/README.md | 229 ++++++++++++++++++++------------------ 1 file changed, 120 insertions(+), 109 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 1ad136f..081407a 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -9,22 +9,19 @@ ## Abstract -In this project, you will build a restaurant order management system using RabbitMQ as a message broker and PostgreSQL for persistent storage. The system simulates a real restaurant workflow where orders go through various processing stages: received -> cooking -> ready -> delivered. - -Similar systems are used in real restaurants and food delivery services. For example, when you order food through an app, your order goes through an analogous processing system with task distribution among different staff members. - -This project will teach you that before you start writing code, you should think through the system architecture, understand how components will interact, and only then proceed to implementation. +In this project, you will build a distributed restaurant order management system. Using Go, you will create several microservices that communicate asynchronously via a RabbitMQ message broker, with order data persisted in a PostgreSQL database. This system will simulate a real-world restaurant workflow, from an order being placed via an API, to it being cooked by a kitchen worker, and finally its status being tracked. This project teaches a fundamental lesson in modern software engineering: think about the architecture first. Before writing a single line of code, you must design how services will interact, how data will flow, and how the system can scale. ## Context -> You can't polish your way out of bad architecture. +> Good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. The ultimate goal is to minimize the lifetime cost of the system and to maximize programmer productivity. > +> — Robert C. Martin (Uncle Bob) -The challenge we've chosen may seem unique, but at its core lies the common structure of many other distributed systems: data comes in, gets processed by various components, and is passed along the chain. +Have you ever ordered a pizza through a delivery app and watched its status change from "Order Placed" to "In the Kitchen" and finally "Out for Delivery"? What seems like a simple status tracker is actually a complex dance between multiple independent systems. The web app where you place your order isn't directly connected to the tablet in the kitchen. -Our specific task is to create a reliable order processing system that can scale horizontally. If we simply processed orders sequentially in a single thread, the system would quickly become a bottleneck as load increases. +This is the power of microservices and message queues. The challenge is to create a reliable order processing system that can handle a high volume of orders without slowing down. A single, monolithic application would quickly become a bottleneck. Instead, we distribute the work. The `Order Service` takes your order, the `Kitchen Service` cooks it, and a `Notification Service` keeps you updated. They don't talk to each other directly; they pass messages through a central mailroom, RabbitMQ. This ensures that even if the kitchen is busy, the order service can still take new orders. -To achieve more efficient results, we need a distributed architecture with separation of concerns, where each component performs its specific function. +In this project, you will build the core of such a system. You will learn how to design services that have a single responsibility and how to orchestrate their collaboration to create a robust and scalable application. **Message Queue Patterns** @@ -32,7 +29,7 @@ A smart way to solve this type of problem is using message queue patterns. This **Work Queue Pattern** - One producer sends tasks to a queue -- Multiple consumers wait for the task to arrive, but only one receives it. +- Multiple consumers wait for the task to arrive, but only one receives it - Each task is processed by exactly one consumer - Provides load distribution among workers @@ -46,60 +43,39 @@ A smart way to solve this type of problem is using message queue patterns. This - Allows creating complex processing schemes - Provides flexibility in defining recipients -Order processing algorithm using queues: +## System Architecture Overview + +Your application will consist of four main services, a database, and a message broker. They interact as follows: ``` -1. Client places order through API -2. Order is placed in kitchen_queue -3. Available cook takes order from queue -4. After cooking, order is directed to quality_queue -5. Quality controller checks the order -6. Ready order is placed in packaging_queue -7. Packager prepares order for delivery -8. Order is directed to courier through delivery_queue -9. All interested parties receive status notifications + +------------------+ + | PostgreSQL DB | + | (Order Storage) | + +--+-------------+-+ + ^ ^ + (Writes & Reads) | | (Writes & Reads) + | | ++------------+ +----------+ v v +---------------+ +| HTTP Client|------->| Order | | Kitchen | +| (e.g. curl)| | Service | | Service | ++------------+ +----------+ +--+------------+ + | ^ + | (Publishes New Order) | (Publishes Status Update) + v | + +-----+-------------------------+-----+ + | | + | RabbitMQ Message Broker | + | | + +-------------------------------------+ + | | + | (Status Updates) | (Status Updates) + v v + +-----+-----------+ +-----+-----------+ + | Notification | | Tracking | + | Service | | Service | + +-----------------+ +-----------------+ ``` -**Data Structure and Architecture** - -Let's consider our program requirements. We aim to process hundreds of orders simultaneously, support multiple workers, and ensure response times in seconds, not minutes. At this scale, we cannot afford inefficient algorithms or architecture. - -The order processing workflow requires coordination between multiple components. This means we need a smart way to organize interaction between services. We're looking for a structure that can efficiently represent order state and its transitions between stages. - -Our program will work in microservice mode: each component (cook, quality controller, packager, courier) works independently but is coordinated through RabbitMQ. Each service must quickly receive appropriate tasks and send results further down the chain. - -Let's call the combination of an order and its current state a "processing stage" - this is a common term in workflow systems. We'll need to track transitions between stages and ensure reliable message delivery. - -This approach gives us a clear path forward. By focusing on these key requirements, we can design a solution that is both elegant and efficient. - -## System Architecture - -### Service Responsibilities - -**Order Service** -- Accept HTTP requests for new orders -- Validate order data and calculate totals -- Store orders in PostgreSQL -- Publish orders to RabbitMQ kitchen queue -- Provide order status API endpoints - -**Kitchen Workers** -- Consume orders from kitchen queue -- Simulate cooking process -- Update order status in database -- Publish status updates to notification exchange - -**Tracking Service** -- Monitor order status changes -- Handle status update requests -- Provide centralized status tracking -- Log all status transitions - -**Notification Subscriber** -- Listen for status update notifications -- Filter notifications by customer -- Display real-time status updates - ## Database Schema ### Orders Table @@ -178,31 +154,36 @@ create table workers ( **Exchange Types Explained:** -**Direct Exchange (`orders_direct`)**: Routes messages to queues based on exact routing key match. Used for targeted order routing where each order type goes to its specific queue. Only workers listening to that exact routing key will receive the message. +**Topic Exchange (`orders_topic`)**: Routes messages to queues based on pattern matching with routing keys. Used for flexible order routing where different order types and priorities can be routed to specialized queues. **Fanout Exchange (`notifications_fanout`)**: Broadcasts all messages to every queue bound to it, ignoring routing keys. Used for notifications where all subscribers need to receive status updates regardless of their specific interests. ``` Exchanges: -├── orders_direct (type: direct, durable: true) -│ └── Routing Keys: -│ ├── kitchen.dine-in -│ ├── kitchen.takeout -│ └── kitchen.delivery +├── orders_topic (type: topic, durable: true) +│ └── Routing Keys (examples): +│ ├── kitchen.dine-in.high +│ ├── kitchen.takeout.medium +│ └── kitchen.delivery.low │ └── notifications_fanout (type: fanout, durable: true) └── Broadcasts to all subscribers Queues: ├── kitchen_queue (durable: true, x-max-priority: 10) +│ └── Bound to orders_topic with routing key: kitchen.order │ └── Read by: General kitchen workers ├── kitchen_dine_in_queue (durable: true, x-max-priority: 10) +│ └── Bound to orders_topic with routing key: kitchen.dine-in.* │ └── Read by: Dine-in specialized workers ├── kitchen_takeout_queue (durable: true, x-max-priority: 10) +│ └── Bound to orders_topic with routing key: kitchen.takeout.* │ └── Read by: Takeout specialized workers ├── kitchen_delivery_queue (durable: true, x-max-priority: 10) +│ └── Bound to orders_topic with routing key: kitchen.delivery.* │ └── Read by: Delivery specialized workers └── notifications_queue (durable: true, auto-delete: false) + └── Bound to notifications_fanout └── Read by: Notification subscribers, customer apps ``` @@ -212,7 +193,7 @@ Queues: **Purpose**: Contains complete order information for kitchen processing **Sent to**: Kitchen queues (kitchen_queue, kitchen_dine_in_queue, etc.) **Read by**: Kitchen Workers -**Routing**: Through orders_direct exchange using routing keys like "kitchen.delivery" +**Routing**: Through orders_topic exchange using routing keys like "kitchen.delivery.high" ```json { @@ -237,7 +218,7 @@ Queues: #### Status Update Message **Purpose**: Notifies all interested parties about order status changes **Sent to**: notifications_fanout exchange -**Read by**: Notification Subscribers, terminal +**Read by**: Notification Subscribers **Routing**: Broadcast to all queues bound to notifications_fanout exchange ```json @@ -253,17 +234,35 @@ Queues: ## Logging Format -All services must implement structured logging in JSON format: +This structured logging format must be used by all services. Consistent logging is vital for debugging, monitoring, and auditing the system. + +### JSON Log Format +All services must implement structured logging in JSON format. + +**Mandatory Core Fields:** +The following keys must always be present in every log entry: +* `timestamp`: The time the log entry was created. +* `level`: The log level (e.g., INFO, ERROR). +* `service`: The name of the service emitting the log (e.g., `order-service`, `kitchen-worker`). +* `action`: A concise, machine-readable string describing the event (e.g., `order_received`, `db_error`). +* `message`: A human-readable description of the event. +* `hostname`: The hostname or unique identifier of the module emitting the log. +* `request_id`: A unique identifier for correlating requests/operations across multiple services. + +**Format Example:** ```json { "timestamp": "2024-12-16T10:30:15.123Z", "level": "INFO", "service": "order-service", - "worker_name": "chef_mario", - "order_number": "ORD_20241216_001", + "version": "abcdef12345", + "hostname": "order-service-789abc", + "request_id": "a1b2c3d4e5f6", "action": "order_received", "message": "Order received and queued for processing", + "worker_name": "chef_mario", + "order_number": "ORD_20241216_001", "duration_ms": 45, "details": { "customer_name": "John Doe", @@ -274,37 +273,40 @@ All services must implement structured logging in JSON format: } ``` -### Log Levels Usage: - -**ERROR**: -- Database connection failures -- RabbitMQ connection drops -- Order validation failures (invalid data, missing required fields) -- Message publish/consume errors -- System crashes or unrecoverable errors - -**WARN**: -- Message retry attempts (when worker is temporarily unavailable) -- Database query timeout warnings -- Configuration issues (missing optional parameters, using defaults) -- Order processing delays (cooking time exceeded) -- Worker disconnection/reconnection events - -**INFO**: -- Order lifecycle events (received, cooking started, completed) -- Worker status changes (connected, disconnected, processing order) -- Service startup/shutdown -- Normal business operations -- Performance milestones (orders per hour, average processing time) - -**DEBUG**: -- Detailed message content (full order JSON) -- Database query execution details -- Internal processing steps -- Performance metrics (exact processing times, queue lengths) -- Development and troubleshooting information +**Error Object Shape:** +For `ERROR` level logs, an `error` object must be included with the following structure: +```json +{ + "timestamp": "2024-12-16T10:35:00.000Z", + "level": "ERROR", + "service": "kitchen-worker", + "version": "abcdef12345", + "hostname": "kitchen-worker-xyz789", + "request_id": "a1b2c3d4e5f6", + "action": "db_query_failed", + "message": "Failed to retrieve order from database", + "error": { + "type": "sql.ErrNoRows", + "msg": "query failed: no rows in result set", + "stack": "internal/db/order.go:120" + } +} +``` + +### Log Levels Usage + +* **ERROR:** Database connection failures, RabbitMQ connection drops, order validation failures, message publish/consume errors, system crashes. +* **WARN:** Message retry attempts, database query timeout warnings, configuration issues, order processing delays, worker disconnection/reconnection events. +* **INFO:** Order lifecycle events (received, cooking started, completed), worker status changes, service startup/shutdown, normal business operations, performance milestones. +* **DEBUG:** Detailed message content, database query execution details, internal processing steps, performance metrics, development and troubleshooting information. + +### Log Location and Format Rules: +* All logs must be emitted as single-line JSON (no pretty printing) to `stdout`. This is crucial for containerized environments where log collectors typically consume `stdout`. +* Logs must not contain Personally Identifiable Information (PII), such as `customer_address`, payment information, or other sensitive data. +* All log messages must be UTF-8 encoded, and newlines within log fields must be properly escaped. ### Required Log Events: + - **Order received/processed/completed** (INFO level) - **Worker connected/disconnected** (INFO level) - **Status changes** (INFO level) @@ -313,22 +315,22 @@ All services must implement structured logging in JSON format: ## Configuration Management -### Database Configuration -Configuration is stored in environment variables and command-line flags. The system creates a configuration file at startup: +A clear configuration strategy is essential for deploying and running the services in different environments. + +### Configuration Files + +The system should be able use configuration files for database and RabbitMQ connections. ```yaml -# config/database.yaml (auto-generated during --setup-db) +# Database Configuration database: host: localhost port: 5432 user: restaurant_user password: restaurant_pass database: restaurant_db -``` -### RabbitMQ Configuration -```yaml -# config/rabbitmq.yaml (auto-generated during --setup-queues) +# RabbitMQ Configuration rabbitmq: host: localhost port: 5672 @@ -336,8 +338,8 @@ rabbitmq: password: guest vhost: / exchanges: - orders_direct: - type: direct + orders_topic: + type: topic durable: true notifications_fanout: type: fanout @@ -346,6 +348,15 @@ rabbitmq: kitchen_queue: durable: true max_priority: 10 + kitchen_dine_in_queue: + durable: true + max_priority: 10 + kitchen_takeout_queue: + durable: true + max_priority: 10 + kitchen_delivery_queue: + durable: true + max_priority: 10 notifications_queue: durable: true auto_delete: false From 84dba7e021ea7d5e4aa1761d763c4bf51bb0fa07 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Fri, 27 Jun 2025 15:43:41 +0500 Subject: [PATCH 13/20] removed: baseline --- wheres-my-pizza/README.md | 407 -------------------------------------- 1 file changed, 407 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 081407a..cbeed0c 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -392,413 +392,6 @@ $ go build -o restaurant-system . By default, your program should implement a basic order processing system using RabbitMQ work queue pattern to distribute orders among cooks. -#### Database Setup: - -Create all required tables as specified in the Database Schema section. Each service must connect to PostgreSQL and handle connection errors gracefully. - -#### RabbitMQ Setup: - -- Queue name: `kitchen_queue` -- Exchange: direct exchange named `orders_direct` -- Routing key: `kitchen.order` -- Queue should be durable and survive server restarts -- Messages should be persistent -- Connection must handle reconnection automatically - -#### Orders: - -1. **Order Reception**: Order Service receives HTTP POST request with order data - - **Validation Rules**: - - customer_name: required, 1-100 characters, no special characters except spaces, hyphens, apostrophes - - order_type: required, must be one of: 'dine-in', 'takeout', 'delivery' - - items: required array, minimum 1 item, maximum 20 items per order - - item.name: required, 1-50 characters - - item.quantity: required, integer, 1-10 per item - - item.price: required, decimal, 0.01-999.99 - - table_number: required for dine-in orders, 1-100 - - delivery_address: required for delivery orders, minimum 10 characters - -2. **Order Calculation**: Calculate total amount and assign priority - - **Total Calculation**: Sum of (item.price * item.quantity) for all items - - **Priority Assignment**: - - VIP customers (customer_type="vip"): priority 10 - - Orders > $100: priority 5 - - Default: priority 1 - -3. **Database Storage**: Store order in PostgreSQL orders table within transaction - - Generate order number: `ORD_YYYYMMDD_NNN` (NNN = daily sequence number) - - Insert order record with status 'received' - - Log initial status in order_status_log - -4. **Message Publishing**: Publish order message to RabbitMQ - - Route to kitchen_queue using routing key 'kitchen.order' - - Message includes complete order data (see Order Message format) - -5. **Kitchen Processing**: Kitchen Worker consumes order from queue - - Acknowledge receipt and update status to 'cooking' - - Record processing start time and worker name - - Simulate cooking process (configurable duration) - -6. **Order Completion**: Update order status to 'ready' - - Record completion timestamp - - Update orders_processed counter for worker - - Log completion details - - Acknowledge message to RabbitMQ - -Outcomes: -- Program accepts orders through REST API -- Program uses PostgreSQL to persist order data -- Program uses RabbitMQ to distribute orders among cooks -- Program handles database and RabbitMQ connection failures -- All status changes are logged to database - -Notes: -- Orders contain: order number, customer info, items list, pricing, timestamps -- Cooks compete for orders (competing consumers pattern) -- Default cooking time: 10 seconds for demo purposes -- Order IDs follow format: `ORD_YYYYMMDD_NNN` - -Constraints: -- On any error, output structured JSON log with error details -- Orders must be acknowledged only after successful database update -- Maximum 50 concurrent orders in system -- Each order must be processed exactly once -- Database transactions must be used for order updates - -Examples: - -```sh -$ docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management -$ docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=password postgres:13 - -# Initialize database and queues (creates config files) -$ ./restaurant-system --setup-db --db="postgres://user:password@localhost/restaurant" -$ ./restaurant-system --setup-queues --rabbitmq="localhost:5672" - -# Start order service (uses config files if flags not provided) -./restaurant-system --mode=order-service --port=3000 -{"timestamp":"2024-12-16T10:30:00Z","level":"INFO","service":"order-service","message":"Service started on port 3000"} - -# Start kitchen worker (uses config files if flags not provided) -./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --order-types="dine-in,takeout" -{"timestamp":"2024-12-16T10:30:05Z","level":"INFO","service":"kitchen-worker","worker_name":"chef_mario","message":"Worker connected and ready"} - -# Place order -$ curl -X POST http://localhost:3000/orders \ - -H "Content-Type: application/json" \ - -d '{ - "customer_name": "John Doe", - "order_type": "takeout", - "items": [ - {"name": "Margherita Pizza", "quantity": 1, "price": 15.99}, - {"name": "Caesar Salad", "quantity": 1, "price": 8.99} - ] - }' - -Response: {"order_number": "ORD_20241216_001", "status": "received", "total_amount": 24.98} -``` - -### Multiple Workers - -Your program should support multiple cooks working simultaneously with automatic order distribution among them. - -#### Worker Management: - -- Register workers in PostgreSQL workers table -- Track worker status and last seen timestamp -- Handle worker disconnections gracefully -- Redistribute unacknowledged orders to available workers - -#### Load Balancing: - -- Use RabbitMQ round-robin distribution by default -- Track worker performance metrics in database -- Support worker-specific order type filtering -- Implement worker health monitoring - -Outcomes: -- Program supports multiple cooks simultaneously -- Orders are distributed evenly among available cooks -- New cooks can connect dynamically without service restart -- Cook disconnection doesn't affect processing of other orders -- Worker status is tracked in database - -Constraints: -- Use RabbitMQ round-robin distribution mechanism -- Maximum 10 active workers simultaneously -- Each cook must have unique worker name -- Unacknowledged messages are redelivered to other workers -- Worker heartbeat every 30 seconds - -```sh -# Start multiple workers with different specializations -$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --order-types="pizza,pasta" & -$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_luigi" --order-types="salad,soup" & -$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_anna" & - -# Worker logs show specialization -{"timestamp":"2024-12-16T10:30:00Z","level":"INFO","service":"kitchen-worker","worker_name":"chef_mario","message":"Worker registered for order types: pizza,pasta"} - -# Monitor worker status -$ curl http://localhost:3000/workers/status -[ - {"worker_name": "chef_mario", "status": "online", "orders_processed": 5, "last_seen": "2024-12-16T10:35:00Z"}, - {"worker_name": "chef_luigi", "status": "online", "orders_processed": 3, "last_seen": "2024-12-16T10:35:01Z"} -] -``` - -### Order Status Tracking - -Your program should implement a comprehensive order status tracking system using publish/subscribe pattern for real-time notifications. - -#### Status Management: - -- Track all status transitions in `order_status_log` table -- Implement status validation (prevent invalid transitions) -- Provide real-time status updates via RabbitMQ -- Support status queries via REST API - -#### Notification System: - -- Use fanout exchange for broadcasting status updates -- Support customer-specific notification filtering -- Log all notification deliveries -- Handle notification delivery failures - -Outcomes: -- Program tracks complete status history for each order -- Order status is updated at each processing stage -- Clients can query current and historical status -- System sends real-time notifications about status changes -- Status transitions are validated and logged - -Constraints: -- Valid statuses: received, cooking, ready, out-for-delivery, delivered, cancelled -- Use fanout exchange named `notifications_fanout` -- All status changes must be atomic (database + message) -- Notifications include timestamp, order details, estimated completion -- API endpoints for status queries and history - -```sh -# Start tracking service -$ ./restaurant-system --mode=tracking-service --db="postgres://user:password@localhost/restaurant" & -{"timestamp":"2024-12-16T10:30:00Z","level":"INFO","service":"tracking-service","message":"Tracking service started"} - -# Start notification subscriber -$ ./restaurant-system --mode=notification-subscriber --customer="John Doe" & -{"timestamp":"2024-12-16T10:30:05Z","level":"INFO","service":"notification-subscriber","message":"Subscribed to notifications for customer: John Doe"} - -# Check order status and history -$ curl http://localhost:3000/orders/ORD_20241216_001/status -{ - "order_number": "ORD_20241216_001", - "current_status": "cooking", - "updated_at": "2024-12-16T10:32:00Z", - "estimated_completion": "2024-12-16T10:42:00Z", - "processed_by": "chef_mario" -} - -$ curl http://localhost:3000/orders/ORD_20241216_001/history -[ - {"status": "received", "timestamp": "2024-12-16T10:30:00Z", "changed_by": "order-service"}, - {"status": "cooking", "timestamp": "2024-12-16T10:32:00Z", "changed_by": "chef_mario"} -] -``` - -### Order Types and Routing - -Your program should support different order types with specialized routing and processing requirements. - -#### Order Type Processing: - -- **Dine-in**: Requires table number, shorter cooking time (8 sec) -- **Takeout**: Standard processing, medium cooking time (10 sec) -- **Delivery**: Requires address validation, longer cooking time (12 sec) - -#### Routing Configuration: - -- Use topic exchange with routing patterns -- Route orders to specialized worker queues -- Support worker subscription to specific order types -- Implement priority routing for rush orders - -Outcomes: -- Program supports three distinct order types with different requirements -- Orders are routed to appropriate processing queues -- Different order types have different processing times and validation -- Delivery orders include address validation and special handling -- Workers can specialize in specific order types - -Constraints: -- Use topic exchange named `orders_topic` with routing keys `kitchen.{type}.{priority}` -- Order type validation on creation with specific field requirements -- Cooking time varies by type: dine-in (8s), takeout (10s), delivery (12s) -- Delivery orders require valid address format -- Table numbers required for dine-in orders - -```sh -# Place different order types -$ curl -X POST http://localhost:3000/orders \ - -d '{"customer_name": "John", "order_type": "dine-in", "table_number": 5, - "items": [{"name": "Steak", "quantity": 1, "price": 25.99}]}' - -$ curl -X POST http://localhost:3000/orders \ - -d '{"customer_name": "Jane", "order_type": "delivery", - "delivery_address": "123 Main St, City, State 12345", - "items": [{"name": "Pizza", "quantity": 1, "price": 18.99}]}' - -# Worker specializing in delivery orders -$ ./restaurant-system --mode=kitchen-worker --worker-name="delivery_chef" --order-types="delivery" -{"timestamp":"2024-12-16T10:30:00Z","level":"INFO","service":"kitchen-worker","worker_name":"delivery_chef","message":"Listening for delivery orders only"} -``` - -### Priority Queue - -Your program should support order prioritization using RabbitMQ priority queues with automatic priority assignment. - -#### Priority Assignment Rules: - -- **Priority 10 (High)**: VIP customers, rush orders, orders > $100 -- **Priority 5 (Medium)**: Large orders ($50-$100), repeat customers -- **Priority 1 (Normal)**: Standard orders - -#### Priority Processing: - -- Configure all queues with x-max-priority: 10 -- Workers process high-priority orders first -- Track priority metrics in database -- Support manual priority override for special cases - -Outcomes: -- Program automatically assigns priorities based on order characteristics -- VIP customers and large orders receive expedited processing -- Workers process orders in priority order -- Priority assignment is logged and auditable -- System supports manual priority overrides - -Constraints: -- All kitchen queues configured with x-max-priority parameter set to 10 -- Priority calculated automatically based on customer type and order value -- VIP status stored in customer database or determined by previous orders -- Priority changes logged in order_status_log table -- High-priority orders bypass normal queue position - -```sh -# VIP customer order (automatically high priority) -$ curl -X POST http://localhost:3000/orders \ - -d '{ - "customer_name": "VIP Customer", - "customer_type": "vip", - "order_type": "delivery", - "delivery_address": "456 Executive Blvd", - "items": [{"name": "Premium Steak", "quantity": 1, "price": 45.99}], - "rush": true - }' - -Response: {"order_number": "ORD_20241216_002", "priority": 10, "status": "received", "estimated_completion": "2024-12-16T10:45:00Z"} - -# Large order (automatically medium priority) -$ curl -X POST http://localhost:3000/orders \ - -d '{ - "customer_name": "Office Catering", - "items": [ - {"name": "Pizza", "quantity": 5, "price": 15.99}, - {"name": "Salad", "quantity": 3, "price": 8.99} - ] - }' - -Response: {"order_number": "ORD_20241216_003", "priority": 5, "total_amount": 106.92} - -# Worker processes by priority -{"timestamp":"2024-12-16T10:32:00Z","level":"INFO","service":"kitchen-worker","worker_name":"chef_mario","order_number":"ORD_20241216_002","message":"Processing HIGH PRIORITY order","priority":10} -``` - -### Usage - -Your program should output comprehensive usage information for all operational modes. - -Outcomes: -- Program outputs detailed usage information for all modes and options -- Includes examples for common usage scenarios -- Documents all configuration parameters -- Provides troubleshooting guidance - -```sh -$ ./restaurant-system --help -Restaurant Order Management System with RabbitMQ and PostgreSQL - -Usage: - restaurant-system --mode=order-service [OPTIONS] - restaurant-system --mode=kitchen-worker --worker-name=worker_name [OPTIONS] - restaurant-system --mode=tracking-service [OPTIONS] - restaurant-system --mode=notification-subscriber [OPTIONS] - restaurant-system --setup-db [OPTIONS] - restaurant-system --setup-queues [OPTIONS] - restaurant-system --help - -Service Modes: - order-service REST API for receiving and managing orders - kitchen-worker Kitchen staff processing orders from queue - tracking-service Centralized order status tracking service - notification-subscriber Real-time customer notification service - -Connection Options: - # If not provided, reads from config.yaml - --db PostgreSQL connection string (required for all modes) - Format: postgres://user:pass@host:port/dbname - --rabbitmq RabbitMQ server address (default: localhost:5672) - --rabbitmq-user RabbitMQ username (default: guest) - --rabbitmq-pass RabbitMQ password (default: guest) - -Order Service Options: - --port HTTP port for REST API (default: 3000) - --max-concurrent Maximum concurrent orders (default: 50) - -Kitchen Worker Options: - --worker-name Unique worker name (required) - --order-types Comma-separated order types to process (default: all) - Options: dine-in,takeout,delivery - --cooking-time Base cooking time in seconds (default: 10) - -Notification Options: - --customer Customer name filter for notifications - --notification-types Types of notifications to receive (default: all) - -Setup Commands: - --setup-db Initialize PostgreSQL database schema - --setup-queues Initialize RabbitMQ exchanges and queues - --migrate-db Run database migrations - -Logging Options: - --log-level Log level: DEBUG, INFO, WARN, ERROR (default: INFO) - --log-file Log file path (default: stdout) - -Examples: - # Setup environment - ./restaurant-system --setup-db --db="postgres://user:pass@localhost/restaurant" - ./restaurant-system --setup-queues --rabbitmq="localhost:5672" - - # Start order service - ./restaurant-system --mode=order-service --port=3000 --db="postgres://user:pass@localhost/restaurant" - - # Start specialized kitchen worker - ./restaurant-system --mode=kitchen-worker --worker-name=pizza_chef --order-types=dine-in,takeout --db="postgres://user:pass@localhost/restaurant" - - # Monitor specific customer notifications - ./restaurant-system --mode=notification-subscriber --customer="John Doe" - -Environment Variables: - DB_URL PostgreSQL connection string - RABBITMQ RabbitMQ server address - LOG_LEVEL Default log level - -Troubleshooting: - - Ensure PostgreSQL and RabbitMQ services are running - - Check connection strings and credentials - - Verify database schema is initialized - - Check RabbitMQ management interface at http://localhost:15672 -``` - ## Support If you get stuck, test your code with the example inputs from the project. You should get the same results. If not, re-read the description again. Perhaps you missed something, or your code is incorrect. From a6d22eeaac2b05ba7ea2eb6a3c41734bbdcf5cd0 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Fri, 27 Jun 2025 19:05:44 +0500 Subject: [PATCH 14/20] fix --- wheres-my-pizza/README.md | 1170 +++++++++++++++++++++++-------------- 1 file changed, 743 insertions(+), 427 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index cbeed0c..3a8619d 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -1,428 +1,744 @@ -# wheres-my-pizza - -## Learning Objectives - -- Message Queue Systems -- RabbitMQ Integration -- Concurrent Programming -- Microservices Architecture - -## Abstract - -In this project, you will build a distributed restaurant order management system. Using Go, you will create several microservices that communicate asynchronously via a RabbitMQ message broker, with order data persisted in a PostgreSQL database. This system will simulate a real-world restaurant workflow, from an order being placed via an API, to it being cooked by a kitchen worker, and finally its status being tracked. This project teaches a fundamental lesson in modern software engineering: think about the architecture first. Before writing a single line of code, you must design how services will interact, how data will flow, and how the system can scale. - -## Context - -> Good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. The ultimate goal is to minimize the lifetime cost of the system and to maximize programmer productivity. -> -> — Robert C. Martin (Uncle Bob) - -Have you ever ordered a pizza through a delivery app and watched its status change from "Order Placed" to "In the Kitchen" and finally "Out for Delivery"? What seems like a simple status tracker is actually a complex dance between multiple independent systems. The web app where you place your order isn't directly connected to the tablet in the kitchen. - -This is the power of microservices and message queues. The challenge is to create a reliable order processing system that can handle a high volume of orders without slowing down. A single, monolithic application would quickly become a bottleneck. Instead, we distribute the work. The `Order Service` takes your order, the `Kitchen Service` cooks it, and a `Notification Service` keeps you updated. They don't talk to each other directly; they pass messages through a central mailroom, RabbitMQ. This ensures that even if the kitchen is busy, the order service can still take new orders. - -In this project, you will build the core of such a system. You will learn how to design services that have a single responsibility and how to orchestrate their collaboration to create a robust and scalable application. - -**Message Queue Patterns** - -A smart way to solve this type of problem is using message queue patterns. This approach views the system as a set of interacting services, where each service processes a specific type of message and passes the result further down the chain. - -**Work Queue Pattern** -- One producer sends tasks to a queue -- Multiple consumers wait for the task to arrive, but only one receives it -- Each task is processed by exactly one consumer -- Provides load distribution among workers - -**Publish/Subscribe Pattern** -- One publisher sends messages to all subscribers -- Multiple consumers receive copies of messages -- Used for notifications and state synchronization - -**Routing Pattern** -- Messages are routed based on routing key -- Allows creating complex processing schemes -- Provides flexibility in defining recipients - -## System Architecture Overview - -Your application will consist of four main services, a database, and a message broker. They interact as follows: - -``` - +------------------+ - | PostgreSQL DB | - | (Order Storage) | - +--+-------------+-+ - ^ ^ - (Writes & Reads) | | (Writes & Reads) - | | -+------------+ +----------+ v v +---------------+ -| HTTP Client|------->| Order | | Kitchen | -| (e.g. curl)| | Service | | Service | -+------------+ +----------+ +--+------------+ - | ^ - | (Publishes New Order) | (Publishes Status Update) - v | - +-----+-------------------------+-----+ - | | - | RabbitMQ Message Broker | - | | - +-------------------------------------+ - | | - | (Status Updates) | (Status Updates) - v v - +-----+-----------+ +-----+-----------+ - | Notification | | Tracking | - | Service | | Service | - +-----------------+ +-----------------+ -``` - -## Database Schema - -### Orders Table -**Purpose**: Primary storage for all restaurant orders with complete order information -**Used by**: Order Service (insert), Kitchen Workers (status updates), Tracking Service (queries) - -```sql -create table orders ( - "id" uuid primary key default gen_random_uuid(), - "created_at" timestamptz not null default now(), - "updated_at" timestamptz not null default now(), - "number" text unique not null, - "customer_name" text not null, - "customer_type" text default 'regular', - "type" text not null check (type in ('dine-in', 'takeout', 'delivery')), - "table_number" integer, - "delivery_address" text, - "total_amount" decimal(10,2) not null, - "priority" integer default 1, - "status" text default 'received', - "processed_by" text, - "completed_at" timestamptz -); -``` - -### Order Items Table -**Purpose**: Stores individual items within each order for detailed order composition -**Used by**: Order Service (insert items when creating orders), API queries for order details - -```sql -CREATE TABLE order_items ( - "id" uuid primary key default gen_random_uuid(), - "created_at" timestamptz not null default now(), - "order_id" uuid references orders(id), - "name" text not null, - "quantity" integer not null, - "price" decimal(8,2) not null -); -``` - -### Order Status Log Table -**Purpose**: Audit trail for all status changes throughout order lifecycle -**Used by**: All services (insert status changes), Tracking Service (status history queries) - -```sql -create table order_status_log ( - "id" uuid primary key default gen_random_uuid(), - "created_at" timestamptz not null default now(), - order_id uuid references orders(id), - "status" text, - "changed_by" text, - "changed_at" timestamp default current_timestamp, - "notes" text -); -``` - -### Workers Table -**Purpose**: Registry and monitoring of all kitchen workers and their current status -**Used by**: Kitchen Workers (registration and heartbeat), Tracking Service (worker monitoring) - -```sql -create table workers ( - "id" uuid primary key default gen_random_uuid(), - "created_at" timestamptz not null default now(), - "name" text unique not null, - "type" text not null, - "status" text default 'online', - "last_seen" timestamp default current_timestamp, - "orders_processed" integer default 0 -); -``` - -## RabbitMQ Configuration - -### Exchanges and Queues Setup - -**Exchange Types Explained:** - -**Topic Exchange (`orders_topic`)**: Routes messages to queues based on pattern matching with routing keys. Used for flexible order routing where different order types and priorities can be routed to specialized queues. - -**Fanout Exchange (`notifications_fanout`)**: Broadcasts all messages to every queue bound to it, ignoring routing keys. Used for notifications where all subscribers need to receive status updates regardless of their specific interests. - -``` -Exchanges: -├── orders_topic (type: topic, durable: true) -│ └── Routing Keys (examples): -│ ├── kitchen.dine-in.high -│ ├── kitchen.takeout.medium -│ └── kitchen.delivery.low -│ -└── notifications_fanout (type: fanout, durable: true) - └── Broadcasts to all subscribers - -Queues: -├── kitchen_queue (durable: true, x-max-priority: 10) -│ └── Bound to orders_topic with routing key: kitchen.order -│ └── Read by: General kitchen workers -├── kitchen_dine_in_queue (durable: true, x-max-priority: 10) -│ └── Bound to orders_topic with routing key: kitchen.dine-in.* -│ └── Read by: Dine-in specialized workers -├── kitchen_takeout_queue (durable: true, x-max-priority: 10) -│ └── Bound to orders_topic with routing key: kitchen.takeout.* -│ └── Read by: Takeout specialized workers -├── kitchen_delivery_queue (durable: true, x-max-priority: 10) -│ └── Bound to orders_topic with routing key: kitchen.delivery.* -│ └── Read by: Delivery specialized workers -└── notifications_queue (durable: true, auto-delete: false) - └── Bound to notifications_fanout - └── Read by: Notification subscribers, customer apps -``` - -### Message Formats - -#### Order Message -**Purpose**: Contains complete order information for kitchen processing -**Sent to**: Kitchen queues (kitchen_queue, kitchen_dine_in_queue, etc.) -**Read by**: Kitchen Workers -**Routing**: Through orders_topic exchange using routing keys like "kitchen.delivery.high" - -```json -{ - "order_number": "ORD_20241216_001", - "customer_name": "John Doe", - "customer_type": "vip", - "order_type": "delivery", - "table_number": null, - "delivery_address": "123 Main St, City", - "items": [ - { - "name": "Margherita Pizza", - "quantity": 1, - "price": 15.99 - } - ], - "total_amount": 15.99, - "priority": 10 -} -``` - -#### Status Update Message -**Purpose**: Notifies all interested parties about order status changes -**Sent to**: notifications_fanout exchange -**Read by**: Notification Subscribers -**Routing**: Broadcast to all queues bound to notifications_fanout exchange - -```json -{ - "order_number": "ORD_20241216_001", - "old_status": "received", - "new_status": "cooking", - "changed_by": "chef_mario", - "timestamp": "2024-12-16T10:32:00Z", - "estimated_completion": "2024-12-16T10:42:00Z" -} -``` - -## Logging Format - -This structured logging format must be used by all services. Consistent logging is vital for debugging, monitoring, and auditing the system. - -### JSON Log Format - -All services must implement structured logging in JSON format. - -**Mandatory Core Fields:** -The following keys must always be present in every log entry: -* `timestamp`: The time the log entry was created. -* `level`: The log level (e.g., INFO, ERROR). -* `service`: The name of the service emitting the log (e.g., `order-service`, `kitchen-worker`). -* `action`: A concise, machine-readable string describing the event (e.g., `order_received`, `db_error`). -* `message`: A human-readable description of the event. -* `hostname`: The hostname or unique identifier of the module emitting the log. -* `request_id`: A unique identifier for correlating requests/operations across multiple services. - -**Format Example:** -```json -{ - "timestamp": "2024-12-16T10:30:15.123Z", - "level": "INFO", - "service": "order-service", - "version": "abcdef12345", - "hostname": "order-service-789abc", - "request_id": "a1b2c3d4e5f6", - "action": "order_received", - "message": "Order received and queued for processing", - "worker_name": "chef_mario", - "order_number": "ORD_20241216_001", - "duration_ms": 45, - "details": { - "customer_name": "John Doe", - "order_type": "delivery", - "priority": 10, - "total_amount": 15.99 - } -} -``` - -**Error Object Shape:** -For `ERROR` level logs, an `error` object must be included with the following structure: -```json -{ - "timestamp": "2024-12-16T10:35:00.000Z", - "level": "ERROR", - "service": "kitchen-worker", - "version": "abcdef12345", - "hostname": "kitchen-worker-xyz789", - "request_id": "a1b2c3d4e5f6", - "action": "db_query_failed", - "message": "Failed to retrieve order from database", - "error": { - "type": "sql.ErrNoRows", - "msg": "query failed: no rows in result set", - "stack": "internal/db/order.go:120" - } -} -``` - -### Log Levels Usage - -* **ERROR:** Database connection failures, RabbitMQ connection drops, order validation failures, message publish/consume errors, system crashes. -* **WARN:** Message retry attempts, database query timeout warnings, configuration issues, order processing delays, worker disconnection/reconnection events. -* **INFO:** Order lifecycle events (received, cooking started, completed), worker status changes, service startup/shutdown, normal business operations, performance milestones. -* **DEBUG:** Detailed message content, database query execution details, internal processing steps, performance metrics, development and troubleshooting information. - -### Log Location and Format Rules: -* All logs must be emitted as single-line JSON (no pretty printing) to `stdout`. This is crucial for containerized environments where log collectors typically consume `stdout`. -* Logs must not contain Personally Identifiable Information (PII), such as `customer_address`, payment information, or other sensitive data. -* All log messages must be UTF-8 encoded, and newlines within log fields must be properly escaped. - -### Required Log Events: - -- **Order received/processed/completed** (INFO level) -- **Worker connected/disconnected** (INFO level) -- **Status changes** (INFO level) -- **Error conditions and recoveries** (ERROR/WARN level) -- **Performance metrics** (INFO/DEBUG level) - -## Configuration Management - -A clear configuration strategy is essential for deploying and running the services in different environments. - -### Configuration Files - -The system should be able use configuration files for database and RabbitMQ connections. - -```yaml -# Database Configuration -database: - host: localhost - port: 5432 - user: restaurant_user - password: restaurant_pass - database: restaurant_db - -# RabbitMQ Configuration -rabbitmq: - host: localhost - port: 5672 - user: guest - password: guest - vhost: / - exchanges: - orders_topic: - type: topic - durable: true - notifications_fanout: - type: fanout - durable: true - queues: - kitchen_queue: - durable: true - max_priority: 10 - kitchen_dine_in_queue: - durable: true - max_priority: 10 - kitchen_takeout_queue: - durable: true - max_priority: 10 - kitchen_delivery_queue: - durable: true - max_priority: 10 - notifications_queue: - durable: true - auto_delete: false -``` - -## Resources - -- [RabbitMQ Documentation](https://www.rabbitmq.com/documentation.html) -- [RabbitMQ Docker Image](https://hub.docker.com/_/rabbitmq) -- [Go AMQP Client](https://github.com/rabbitmq/amqp091-go) -- [PostgreSQL Go Driver (pgx)](https://github.com/jackc/pgx) - -## General Criteria - -- Your code MUST be written in accordance with [gofumpt](https://github.com/mvdan/gofumpt). If not, you will automatically receive a score of `0`. -- Your program MUST compile successfully. -- Your program MUST NOT crash unexpectedly (any panics: `nil-pointer dereference`, `index out of range`, etc.). If this happens, you will receive `0` points during defense. -- Only built-in Go packages, `pgx/v5` and the official AMQP client (`github.com/rabbitmq/amqp091-go`) are allowed. If other packages are used, you will receive a score of `0`. -- RabbitMQ server MUST be running and available for connection. -- PostgreSQL database MUST be running and accessible for all services -- All RabbitMQ connections must handle reconnection scenarios -- Implement proper graceful shutdown for all services -- All database operations must be transactional where appropriate -- The project MUST compile with the following command in the project root directory: - -```sh -$ go build -o restaurant-system . -``` - -## Mandatory Part - -### Baseline - -By default, your program should implement a basic order processing system using RabbitMQ work queue pattern to distribute orders among cooks. - -## Support - -If you get stuck, test your code with the example inputs from the project. You should get the same results. If not, re-read the description again. Perhaps you missed something, or your code is incorrect. - -Make sure both PostgreSQL and RabbitMQ servers are running and accessible. Check the connection strings and verify proper configuration of database schema and message queues. - -Test each component individually before integrating them together. Use the provided SQL scripts to verify database schema and the RabbitMQ management interface to monitor queue status. - -If you're still stuck, review the logging output for error details, check service dependencies, and ensure all required environment variables are set correctly. - -## Guidelines from Author - -Before diving into code, it's crucial to step back and think about your system architecture. This project illustrates a fundamental principle of good software design - your architectural choices often determine the clarity and efficiency of your code. - -Start with questions: How will components interact? Which message exchange patterns best fit the task? What delivery guarantees are needed? How will you handle failures and ensure data consistency? Only after you've carefully thought through these questions should you proceed to API design and code writing. - -This approach may seem like extra work initially, but it pays off. Well-chosen architecture can make your code simpler, more readable, and often more efficient. It's like choosing the right tools before starting work - with the right foundation, the rest of the work becomes much easier. - -Pay special attention to the data flow between services. Design your database schema first, then define your message formats, and finally implement the service logic. This order ensures consistency and reduces the need for major refactoring later. - -Remember that in programming, as in many other things, thoughtful preparation is the key to success. Spend time on proper architecture, and you'll find that the coding process becomes smoother and more enjoyable. - -Good system architecture is the foundation of clear, efficient code. It often simplifies programming more than clever algorithms can. Invest time in architectural design first. This approach usually leads to more maintainable and understandable programs, regardless of their size or complexity. - -## Author - -This project has been created by: - -Yelnar Moldabekov - -Contacts: - -- Email: [mranle91@gmail.com](mailto:mranle91@gmail.com) +# wheres-my-pizza + +## Learning Objectives + +- Message Queue Systems +- RabbitMQ Integration +- Concurrent Programming +- Microservices Architecture + +## Abstract + +In this project, you will build a distributed restaurant order management system. Using Go, you will create several microservices that communicate asynchronously via a RabbitMQ message broker, with order data persisted in a PostgreSQL database. This system will simulate a real-world restaurant workflow, from an order being placed via an API, to it being cooked by a kitchen worker, and finally its status being tracked. This project teaches a fundamental lesson in modern software engineering: think about the architecture first. Before writing a single line of code, you must design how services will interact, how data will flow, and how the system can scale. + +## Context + +> Good architecture makes the system easy to understand, easy to develop, easy to maintain, and easy to deploy. The ultimate goal is to minimize the lifetime cost of the system and to maximize programmer productivity. +> +> — Robert C. Martin (Uncle Bob) + +Have you ever ordered a pizza through a delivery app and watched its status change from "Order Placed" to "In the Kitchen" and finally "Out for Delivery"? What seems like a simple status tracker is actually a complex dance between multiple independent systems. The web app where you place your order isn't directly connected to the tablet in the kitchen. + +This is the power of microservices and message queues. The challenge is to create a reliable order processing system that can handle a high volume of orders without slowing down. A single, monolithic application would quickly become a bottleneck. Instead, we distribute the work. The `Order Service` takes your order, the `Kitchen Service` cooks it, and a `Notification Subscriber` keeps you updated. They don't talk to each other directly; they pass messages through a central mailroom, RabbitMQ. This ensures that even if the kitchen is busy, the order service can still take new orders. + +In this project, you will build the core of such a system. You will learn how to design services that have a single responsibility and how to orchestrate their collaboration to create a robust and scalable application. + +**Message Queue Patterns** + +A smart way to solve this type of problem is using message queue patterns. This approach views the system as a set of interacting services, where each service processes a specific type of message and passes the result further down the chain. + +**Work Queue Pattern** +- One producer sends tasks to a queue +- Multiple consumers wait for the task to arrive, but only one receives it +- Each task is processed by exactly one consumer +- Provides load distribution among workers + +**Publish/Subscribe Pattern** +- One publisher sends messages to all subscribers +- Multiple consumers receive copies of messages +- Used for notifications and state synchronization + +**Routing Pattern** +- Messages are routed based on routing key +- Allows creating complex processing schemes +- Provides flexibility in defining recipients + +## System Architecture Overview + +Your application will consist of four main services, a database, and a message broker. They interact as follows: + +``` + +------------------+ + | PostgreSQL DB | + | (Order Storage) | + +--+-------------+-+ + ^ ^ + (Writes & Reads) | | (Writes & Reads) + | | ++------------+ +----------+ v v +---------------+ +| HTTP Client|------->| Order | | Kitchen | +| (e.g. curl)| | Service | | Service | ++------------+ +----------+ +--+------------+ + | ^ + | (Publishes New Order) | (Publishes Status Update) + v | + +-----+-------------------------+-----+ + | | + | RabbitMQ Message Broker | + | | + +-------------------------------------+ + | | + | (Status Updates) | (Status Updates) + v v + +-----+-----------+ +-----+-----------+ + | Notification | | Tracking | + | Subscriber | | Service | + +-----------------+ +-----------------+ +``` + +## Database Schema + +### Orders Table +**Purpose**: Primary storage for all restaurant orders with complete order information +**Used by**: Order Service (insert), Kitchen Workers (status updates), Tracking Service (queries) + +```sql +create table orders ( + "id" SERIAL PRIMARY KEY, + "created_at" timestamptz not null default now(), + "updated_at" timestamptz not null default now(), + "number" text unique not null, + "customer_name" text not null, + "customer_type" text default 'regular', + "type" text not null check (type in ('dine_in', 'takeout', 'delivery')), + "table_number" integer, + "delivery_address" text, + "total_amount" decimal(10,2) not null, + "priority" integer default 1, + "status" text default 'received', + "processed_by" text, + "completed_at" timestamptz +); +``` + +### Order Items Table +**Purpose**: Stores individual items within each order for detailed order composition +**Used by**: Order Service (insert items when creating orders), API queries for order details + +```sql +create table order_items ( + "id" SERIAL PRIMARY KEY, + "created_at" timestamptz not null default now(), + "order_id" integer references orders(id), + "name" text not null, + "quantity" integer not null, + "price" decimal(8,2) not null +); +``` + +### Order Status Log Table +**Purpose**: Audit trail for all status changes throughout order lifecycle +**Used by**: All services (insert status changes), Tracking Service (status history queries) + +```sql +create table order_status_log ( + "id" SERIAL PRIMARY KEY, + "created_at" timestamptz not null default now(), + "order_id" integer references orders(id), + "status" text, + "changed_by" text, + "changed_at" timestamptz default current_timestamp, + "notes" text +); +``` + +### Workers Table +**Purpose**: Registry and monitoring of all kitchen workers and their current status +**Used by**: Kitchen Workers (registration and heartbeat), Tracking Service (worker monitoring) + +```sql +create table workers ( + "id" SERIAL PRIMARY KEY, + "created_at" timestamptz not null default now(), + "name" text unique not null, + "type" text not null, + "status" text default 'online', + "last_seen" timestamptz default current_timestamp, + "orders_processed" integer default 0 +); +``` + +## RabbitMQ Configuration + +### Exchanges and Queues Setup + +**Exchange Types Explained:** + +**Topic Exchange (`orders_topic`)**: Routes messages to queues based on pattern matching with routing keys. Used for flexible order routing where different order types and priorities can be routed to specialized queues. + +**Fanout Exchange (`notifications_fanout`)**: Broadcasts all messages to every queue bound to it, ignoring routing keys. Used for notifications where all subscribers need to receive status updates regardless of their specific interests. + +``` +Exchanges: +├── orders_topic (type: topic, durable: true) +│ └── Routing Keys (examples): +│ ├── kitchen.dine_in.10 +│ ├── kitchen.takeout.5 +│ └── kitchen.delivery.1 +│ +└── notifications_fanout (type: fanout, durable: true) + └── Broadcasts to all subscribers + +Queues: +├── kitchen_queue (durable: true, x-max-priority: 10) +│ └── Bound to orders_topic with routing key: kitchen.{order_type}.{priority} +│ └── Read by: General kitchen workers +├── kitchen_dine_in_queue (durable: true, x-max-priority: 10) +│ └── Bound to orders_topic with routing key: kitchen.dine_in.* +│ └── Read by: dine_in specialized workers +├── kitchen_takeout_queue (durable: true, x-max-priority: 10) +│ └── Bound to orders_topic with routing key: kitchen.takeout.* +│ └── Read by: Takeout specialized workers +├── kitchen_delivery_queue (durable: true, x-max-priority: 10) +│ └── Bound to orders_topic with routing key: kitchen.delivery.* +│ └── Read by: Delivery specialized workers +└── notifications_queue (durable: true, auto-delete: false) + └── Bound to notifications_fanout + └── Read by: Notification subscribers, customer apps +``` + +### Message Formats + +#### Order Message +**Purpose**: Contains complete order information for kitchen processing +**Sent to**: Kitchen queues (kitchen_queue, kitchen_dine_in_queue, etc.) +**Read by**: Kitchen Workers +**Routing**: Through orders_topic exchange using routing keys like "kitchen.delivery.10" + +```json +{ + "order_number": "ORD_20241216_001", + "customer_name": "John Doe", + "customer_type": "vip", + "order_type": "delivery", + "table_number": null, + "delivery_address": "123 Main St, City", + "items": [ + { + "name": "Margherita Pizza", + "quantity": 1, + "price": 15.99 + } + ], + "total_amount": 15.99, + "priority": 10 +} +``` + +#### Status Update Message +**Purpose**: Notifies all interested parties about order status changes +**Sent to**: `notifications_fanout` exchange +**Read by**: Notification Subscribers +**Routing**: Broadcast to all queues bound to `notifications_fanout` exchange + +```json +{ + "order_number": "ORD_20241216_001", + "old_status": "received", + "new_status": "cooking", + "changed_by": "chef_mario", + "timestamp": "2024-12-16T10:32:00Z", + "estimated_completion": "2024-12-16T10:42:00Z" +} +``` + +## Logging Format + +This structured logging format must be used by all services. Consistent logging is vital for debugging, monitoring, and auditing the system. + +### JSON Log Format + +All services must implement structured logging in JSON format. + +**Mandatory Core Fields:** +The following keys must always be present in every log entry: +* `timestamp`: The time the log entry was created. +* `level`: The log level (e.g., INFO, ERROR). +* `service`: The name of the service emitting the log (e.g., `order-service`, `kitchen-worker`). +* `action`: A concise, machine-readable string describing the event (e.g., `order_received`, `db_error`). +* `message`: A human-readable description of the event. +* `hostname`: The hostname or unique identifier of the module emitting the log. +* `request_id`: A unique identifier for correlating requests/operations across multiple services. + +**Format Example:** +```json +{ + "timestamp": "2024-12-16T10:30:15.123Z", + "level": "INFO", + "service": "order-service", + "hostname": "order-service-789abc", + "request_id": "a1b2c3d4e5f6", + "action": "order_received", + "message": "Order received and queued for processing", + "worker_name": "chef_mario", + "order_number": "ORD_20241216_001", + "duration_ms": 45, + "details": { + "customer_name": "John Doe", + "order_type": "delivery", + "priority": 10, + "total_amount": 15.99 + } +} +``` + +**Error Object Shape:** +For `ERROR` level logs, an `error` object must be included with the following structure: +```json +{ + "timestamp": "2024-12-16T10:35:00.000Z", + "level": "ERROR", + "service": "kitchen-worker", + "hostname": "kitchen-worker-xyz789", + "request_id": "a1b2c3d4e5f6", + "action": "db_query_failed", + "message": "Failed to retrieve order from database", + "error": { + "type": "sql.ErrNoRows", + "msg": "query failed: no rows in result set", + "stack": "internal/db/order.go:120" + } +} +``` + +### Log Levels Usage + +* **ERROR:** Database connection failures, RabbitMQ connection drops, order validation failures, message publish/consume errors, system crashes. +* **WARN:** Message retry attempts, database query timeout warnings, configuration issues, order processing delays, worker disconnection/reconnection events. +* **INFO:** Order lifecycle events (received, cooking started, completed), worker status changes, service startup/shutdown, normal business operations, performance milestones. +* **DEBUG:** Detailed message content, database query execution details, internal processing steps, performance metrics, development and troubleshooting information. + +### Log Location and Format Rules: +* All logs must be emitted as single-line JSON (no pretty printing) to `stdout`. This is crucial for containerized environments where log collectors typically consume `stdout`. +* Logs must not contain Personally Identifiable Information (PII), such as `customer_address`, payment information, or other sensitive data. +* All log messages must be UTF-8 encoded, and newlines within log fields must be properly escaped. + +### Required Log Events: + +- **Order received/processed/completed** (INFO level) +- **Worker connected/disconnected** (INFO level) +- **Status changes** (INFO level) +- **Error conditions and recoveries** (ERROR/WARN level) +- **Performance metrics** (INFO/DEBUG level) + +## Configuration Management + +A clear configuration strategy is essential for deploying and running the services in different environments. + +### Configuration Files + +The system should be able use configuration files for database and RabbitMQ connections. + +```yaml +# Database Configuration +database: + host: localhost + port: 5432 + user: restaurant_user + password: restaurant_pass + database: restaurant_db + +# RabbitMQ Configuration +rabbitmq: + host: localhost + port: 5672 + user: guest + password: guest + vhost: / + exchanges: + orders_topic: + type: topic + durable: true + notifications_fanout: + type: fanout + durable: true + queues: + kitchen_queue: + durable: true + x_max_priority: 10 + kitchen_dine_in_queue: + durable: true + x_max_priority: 10 + kitchen_takeout_queue: + durable: true + x_max_priority: 10 + kitchen_delivery_queue: + durable: true + x_max_priority: 10 + notifications_queue: + durable: true + auto_delete: false +``` + +## Resources + +- [RabbitMQ Documentation](https://www.rabbitmq.com/documentation.html) +- [RabbitMQ Docker Image](https://hub.docker.com/_/rabbitmq) +- [Go AMQP Client](https://github.com/rabbitmq/amqp091-go) +- [PostgreSQL Go Driver (pgx)](https://github.com/jackc/pgx/v5) + +## General Criteria + +- Your code MUST be written in accordance with [gofumpt](https://github.com/mvdan/gofumpt). If not, you will automatically receive a score of `0`. +- Your program MUST compile successfully. +- Your program MUST NOT crash unexpectedly (any panics: `nil-pointer dereference`, `index out of range`, etc.). If this happens, you will receive `0` points during defense. +- Only built-in Go packages, `pgx/v5` and the official AMQP client (`github.com/rabbitmq/amqp091-go`) are allowed. If other packages are used, you will receive a score of `0`. +- RabbitMQ server MUST be running and available for connection. +- PostgreSQL database MUST be running and accessible for all services +- All RabbitMQ connections must handle reconnection scenarios +- Implement proper graceful shutdown for all services +- All database operations must be transactional where appropriate +- The project MUST compile with the following command in the project root directory: + +```sh +$ go build -o restaurant-system . +``` + +## Mandatory Part + +### Baseline + +By default, your program should implement a basic order processing system using RabbitMQ work queue pattern to distribute orders among cooks. + +#### Database Setup: + +Create all required tables as specified in the Database Schema section. Each service must connect to PostgreSQL and handle connection errors gracefully. + +#### RabbitMQ Setup: + + - Queue name: `kitchen_queue` + - Exchange: topic exchange named `orders_topic` + - Routing key: `kitchen.{order_type}.{priority}` (e.g., `kitchen.delivery.10`). `order_type` can be `dine_in`, `takeout`, `delivery`. `priority` can be `10`, `5`, `1`. + - Queue should be durable and survive server restarts + - Messages should be persistent + - Connection must handle reconnection automatically + +### Order Service + +#### API Endpoint + + * **Endpoint:** `POST /orders` + * **Content-Type:** `application/json` + +#### Incoming Request Format + +```json +{ + "customer_name": "John Doe", + "order_type": "takeout", + "items": [ + {"name": "Margherita Pizza", "quantity": 1, "price": 15.99}, + {"name": "Caesar Salad", "quantity": 1, "price": 8.99} + ] +} +``` + +#### Processing Logic + +1. **Receive HTTP POST request.** +2. **Validate data** against rules. +3. **Calculate `total_amount`**. +4. **Assign `priority`** based on rules (see [Priority Assignment Rules](#priority-assignment-rules) below). +5. **Generate `order_number`** (`ORD_YYYYMMDD_NNN`). This should be a daily sequence starting at 001, based on the UTC date. The sequence number should be managed either by a database counter or a transaction-safe query to determine the next number for the current UTC day. +6. **Store order** in PostgreSQL `orders` and `order_items` tables within a transaction. +7. **Log initial status** to `order_status_log`. +8. **Publish `Order Message` to RabbitMQ**: + * **Exchange:** `orders_topic`. + * **Routing Key:** `kitchen.{order_type}.{priority}` (e.g., `kitchen.takeout.1`). + * **Message Properties:** Set `delivery_mode: 2` (persistent). For priority queueing, map the priority level (`10`/`5`/`1`) to a numeric value and set it in the `priority` message property. +9. **Return HTTP JSON response.** + +#### Outgoing Response Format + +```json +{ + "order_number": "ORD_20241216_001", + "status": "received", + "total_amount": 24.98 +} +``` + +#### Priority Assignment Rules + +The system assigns a priority level (`10`, `5`, `1`) to each order. If an order matches multiple criteria, the highest applicable priority is assigned. This priority level is used in the routing key. + +| Priority | Criteria | +| :------- | :-------------------------------------------------------------------- | +| `10` | Order total amount is greater than $100. | +| `5` | Order total amount is between $50 and $100. | +| `1` | All other standard orders. | + +#### Validation Rules & Edge Cases + + * `customer_name`: required, 1-100 characters, no special characters except spaces, hyphens, apostrophes. + * `order_type`: required, must be one of: 'dine_in', 'takeout', 'delivery'. + * `items`: required array, minimum 1 item, maximum 20 items per order. + * `item.name`: required, 1-50 characters. + * `item.quantity`: required, integer, 1-10 per item. + * `item.price`: required, decimal, 0.01-999.99. + * `table_number`: required for `dine_in` orders, 1-100. + * `delivery_address`: required for `delivery` orders, minimum 10 characters. + * **Conflicting Fields**: + * `dine_in` orders must NOT include `delivery_address`. + * `delivery` orders must NOT include `table_number`. + * **Duplicate Items**: Reject order if `items` contains duplicate entries by `name` that would violate the `quantity` rule (e.g., two "Margherita Pizza" entries summing to more than 10 quantity). + * **Database Transactions**: All database operations for order creation must be transactional. + +#### Log Requirements + +Use structured logging format as defined in the [Logging Format](#logging-format) section. + +#### Flags + + * `--mode`: Service mode (required: `order-service`) + * `--port`: HTTP port for REST API (default: 3000) + * `--max-concurrent`: Maximum concurrent orders (default: 50). + +#### Worked Example + +```sh +# Start the Order Service +./restaurant-system --mode=order-service --port=3000 --max-concurrent=50 +# Expected console output: +{"timestamp":"2024-12-16T10:30:00.000Z","level":"INFO","service":"order-service","hostname":"order-service-789abc","request_id":"startup-001","action":"service_started","message":"Order Service started on port 3000","details":{"port":3000,"max_concurrent":50}} +{"timestamp":"2024-12-16T10:30:01.000Z","level":"INFO","service":"order-service","hostname":"order-service-789abc","request_id":"startup-001","action":"db_connected","message":"Connected to PostgreSQL database","duration_ms":250} +{"timestamp":"2024-12-16T10:30:02.000Z","level":"INFO","service":"order-service","hostname":"order-service-789abc","request_id":"startup-001","action":"rabbitmq_connected","message":"Connected to RabbitMQ exchange 'orders_topic'","duration_ms":150} +``` + +```sh +# Place order +curl -X POST http://localhost:3000/orders \ + -H "Content-Type: application/json" \ + -d '{ + "customer_name": "John Doe", + "order_type": "takeout", + "items": [ + {"name": "Margherita Pizza", "quantity": 1, "price": 15.99}, + {"name": "Caesar Salad", "quantity": 1, "price": 8.99} + ] + }' + +# Expected HTTP Response: +# {"order_number": "ORD_20241216_001", "status": "received", "total_amount": 24.98} +``` + +### Order Types + +#### Order Type Processing + + * **dine_in:** + * Requires `table_number`. + * Shorter cooking time (8 seconds). + * **takeout:** + * Standard processing. + * Medium cooking time (10 seconds). + * **delivery:** + * Requires `delivery_address`. + * Longer cooking time (12 seconds). + +#### Worker Specialization + + * Workers can be configured to only accept certain types of orders using the `--order-types` flag. If a worker receives an order it cannot process, it must negatively acknowledge the message (`basic.nack`) and requeue it so another worker can pick it up. + +### Kitchen Worker + +#### Incoming Message Format + + * **Queue:** `kitchen_queue` (or specialized queues like `kitchen_delivery_queue`) + * **Format:** See [Order Message](#order-message) section + +#### Processing Logic + +1. **Consume Message:** Consume an order message from a bound kitchen queue. +2. **Process Order:** + * Update the order's `status` to `cooking` in the `orders` table. + * Log the status change in the `order_status_log` table. + * Publish a `status_update` message to the `notifications_fanout` exchange. +3. **Simulate Cooking:** Simulate the cooking process with a configurable duration. +4. **Update Status to 'ready':** + * Update the order's `status` to `ready` in the `orders` table. + * Update the `completed_at` timestamp. + * Increment the `orders_processed` counter for the worker in the `workers` table. + * Log the status change in the `order_status_log` table. + * Publish a `status_update` message to the `notifications_fanout` exchange. +5. **Acknowledge Receipt:** Acknowledge the message to RabbitMQ (`basic.ack`). +6. **Handle Processing Failure:** If any step in processing fails (e.g., database error, message publish error), reject the message with `basic.nack(requeue=true)` to return it to the queue for redelivery. + +#### Log Requirements + +Use structured logging format as defined in the [Logging Format](#logging-format) section. + +#### Relevant Flags + + * `--port`: HTTP port for REST API (default: 3001) + * `--worker-name`: Unique name for the worker (required). + * `--order-types`: Comma-separated list of order types the worker can process (e.g., `dine_in,takeout`). + +#### Worked Example + +```sh +# Start multiple workers with different specializations +$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --order-types="dine_in" & +$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_luigi" --order-types="delivery" & +$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_anna" & + +# Monitor worker status +$ curl http://localhost:3001/workers/status +[ + {"worker_name": "chef_mario", "status": "online", "orders_processed": 5, "last_seen": "2024-12-16T10:35:00Z"}, + {"worker_name": "chef_luigi", "status": "online", "orders_processed": 3, "last_seen": "2024-12-16T10:35:01Z"} +] +``` + +### Multiple Workers & Load Balancing + +#### Worker Management + + * **Registration:** When a `kitchen-worker` starts, it should register itself in the `workers` table in the database. + * **Status Tracking:** The worker's status (`online`, `offline`, `processing`) and `last_seen` should be updated regularly. + * `online` indicates the worker is running and ready to receive orders. + * `processing` should be set when the worker is actively handling an order. + * `offline` should be set during graceful shutdown. + +#### Heartbeat & Offline Detection + + * Each `kitchen-worker` sends a heartbeat every `--heartbeat-interval` (default 30 seconds) by executing an `UPDATE` query on the `workers` table: `UPDATE workers SET last_seen = now(), status='online' WHERE name = `. This indicates the worker is alive and active. + * The `tracking-service` (or a dedicated monitoring component) should periodically check the `workers` table. A worker is considered `offline` if `now() - last_seen` is greater than `2 * --heartbeat-interval`. + +#### Load Balancing + + * **Round-Robin:** By default, RabbitMQ will distribute orders to available workers in a round-robin fashion. + * **Priority Queues:** If queues are configured with `x-max-priority`, RabbitMQ delivers higher-priority messages before lower-priority regardless of round-robin. + * **Worker Specialization:** Workers can be configured to only accept certain types of orders using the `--order-types` flag. + +#### Redelivery Algorithm + + * **Prefetch Count:** Each `kitchen-worker` should configure its RabbitMQ consumer with `basic.qos(prefetch_count=N)` (where N is the `--prefetch` flag, default 1). This limits the number of unacknowledged messages a worker can receive at a time. + * **Clean Disconnection:** If a worker disconnects cleanly (e.g., graceful shutdown), it should `basic.nack(requeue=true)` any outstanding unacknowledged deliveries. This ensures messages are returned to the queue for other workers. + * **Unclean Disconnection/Crash:** If a worker crashes or disconnects uncleanly, RabbitMQ will automatically re-queue any unacknowledged messages after a timeout, making them available to other workers. + +#### Log Requirements + +Use structured logging format as defined in the [Logging Format](#logging-format) section. + +#### Config / Flags + + * `--worker-name`: Unique worker name (required). + * `--order-types`: Comma-separated order types to process. + * `--heartbeat-interval`: Interval in seconds for sending worker heartbeats (default: 30). + * `--prefetch`: RabbitMQ prefetch count for the worker (default: 1). + +#### Validation Rules & Edge Cases + + * **Duplicate Worker Name**: If the `INSERT` into `workers` violates name uniqueness, log `ERROR` and exit with status code `1`. + * **Graceful Shutdown**: Upon receiving a shutdown signal (e.g., `SIGINT`, `SIGTERM`), a worker should immediately stop accepting new orders from the queue. It must then finish processing any in-flight order, set its status to `offline` in the `workers` table, attempt to `basic.nack(requeue=true)` any unacknowledged messages, and then gracefully exit. This ensures that no work is lost and the system state remains consistent. + * **Database Unreachable**: Handle scenarios where the database is temporarily unreachable during heartbeat updates or registration. Implement retry mechanisms (e.g., three attempts with exponential back-off starting at 1 second, capped at 30 seconds) for database write operations. + +### Tracking Service + +This service provides HTTP API endpoints for querying order status, history, and worker information. It reads from the database and does not produce or consume messages. + +#### API Endpoints + + * **`GET /orders/{order_number}/status`:** + + * **Purpose:** To get the current status of a specific order. + * **Response Format:** + ```json + { + "order_number": "ORD_20241216_001", + "current_status": "cooking", + "updated_at": "2024-12-16T10:32:00Z", + "estimated_completion": "2024-12-16T10:42:00Z", + "processed_by": "chef_mario" + } + ``` + + * **`GET /orders/{order_number}/history`:** + + * **Purpose:** To get the full status history of an order. + * **Response Format:** + ```json + [ + {"status": "received", "timestamp": "2024-12-16T10:30:00Z", "changed_by": "order-service"}, + {"status": "cooking", "timestamp": "2024-12-16T10:32:00Z", "changed_by": "chef_mario"} + ] + ``` + + * **`GET /workers/status`:** + + * **Purpose:** To get the status of all registered kitchen workers. + * **Response Format:** + ```json + [ + {"worker_name": "chef_mario", "status": "online", "orders_processed": 5, "last_seen": "2024-12-16T10:35:00Z"}, + {"worker_name": "chef_luigi", "status": "online", "orders_processed": 3, "last_seen": "2024-12-16T10:35:01Z"} + ] + ``` + +#### Flags + + * `--mode`: Service mode (required: `tracking-service`) + * `--port`: HTTP port for REST API (default: 3002) + +### Notification Service + +This service is responsible for consuming status update messages and notifying relevant parties. It demonstrates the publish/subscribe pattern, where multiple subscribers can listen for events without the publisher (Kitchen Worker) knowing about them. + +#### Incoming Message Format + +- **Queue:** `notifications_queue` (bound to `notifications_fanout` exchange) +- **Format:** See [Status Update Message](#status-update-message) section + +#### Processing Logic + +1. **Consume Message:** Consume a status update message from the `notifications_queue`. +2. **Display Notification:** Print the received notification to standard output in a clear, human-readable format. +3. **Acknowledge Receipt:** Acknowledge the message to RabbitMQ (`basic.ack`) to remove it from the queue. + +#### Log Requirements + +Use structured logging format as defined in the [Logging Format](#logging-format) section for events like service startup and message consumption. + +#### Flags + +- `--mode`: Service mode (required: `notification-subscriber`) + +#### Worked Example + +```sh +# Start a subscriber to listen for all notifications +./restaurant-system --mode=notification-subscriber + +# Start another subscriber interested only in notifications for "John Doe" +./restaurant-system --mode=notification-subscriber + +# Expected console output for the general subscriber when an order status changes: +# Notification for order ORD_20241216_001: Status changed from 'received' to 'cooking' by chef_mario. +{"timestamp":"2024-12-16T10:32:05.000Z","level":"INFO","service":"notification-subscriber","hostname":"notification-sub-1","request_id":"a1b2c3d4e5f6","action":"notification_received","message":"Received status update for order ORD_20241216_001","details":{"order_number":"ORD_20241216_001","new_status":"cooking"}} +``` + +## Support + +If you get stuck, test your code with the example inputs from the project. You should get the same results. If not, re-read the description again. Perhaps you missed something, or your code is incorrect. + +Make sure both PostgreSQL and RabbitMQ servers are running and accessible. Check the connection strings and verify proper configuration of database schema and message queues. + +Test each component individually before integrating them together. Use the provided SQL scripts to verify database schema and the RabbitMQ management interface to monitor queue status. + +If you're still stuck, review the logging output for error details, check service dependencies, and ensure all required environment variables are set correctly. + +## Guidelines from Author + +Before diving into code, it's crucial to step back and think about your system architecture. This project illustrates a fundamental principle of good software design - your architectural choices often determine the clarity and efficiency of your code. + +Start with questions: How will components interact? Which message exchange patterns best fit the task? What delivery guarantees are needed? How will you handle failures and ensure data consistency? Only after you've carefully thought through these questions should you proceed to API design and code writing. + +This approach may seem like extra work initially, but it pays off. Well-chosen architecture can make your code simpler, more readable, and often more efficient. It's like choosing the right tools before starting work - with the right foundation, the rest of the work becomes much easier. + +Pay special attention to the data flow between services. Design your database schema first, then define your message formats, and finally implement the service logic. This order ensures consistency and reduces the need for major refactoring later. + +Remember that in programming, as in many other things, thoughtful preparation is the key to success. Spend time on proper architecture, and you'll find that the coding process becomes smoother and more enjoyable. + +Good system architecture is the foundation of clear, efficient code. It often simplifies programming more than clever algorithms can. Invest time in architectural design first. This approach usually leads to more maintainable and understandable programs, regardless of their size or complexity. + +## Author + +This project has been created by: + +Yelnar Moldabekov + +Contacts: + +- Email: [mranle91@gmail.com](mailto:mranle91@gmail.com) - [GitHub](https://github.com/ymoldabe/) \ No newline at end of file From e59e139177bc70b8b7f1135508126b7d8e85c3b7 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Fri, 27 Jun 2025 19:14:24 +0500 Subject: [PATCH 15/20] update: abstract and questions --- wheres-my-pizza/ABSTRACT.md | 27 +- wheres-my-pizza/QUESTIONS.md | 564 +++++++++++++---------------------- 2 files changed, 223 insertions(+), 368 deletions(-) diff --git a/wheres-my-pizza/ABSTRACT.md b/wheres-my-pizza/ABSTRACT.md index 38db333..9bc57bb 100644 --- a/wheres-my-pizza/ABSTRACT.md +++ b/wheres-my-pizza/ABSTRACT.md @@ -1,15 +1,12 @@ -# wheres-my-pizza - -## Learning Objectives - -- Message Queue Systems -- RabbitMQ Integration -- Concurrent Programming - -## Abstract - -In this project, you will build a restaurant order management system using RabbitMQ as a message broker. The system simulates a real restaurant workflow where orders go through various processing stages: received -> cooking -> ready -> delivered. - -Similar systems are used in real restaurants and food delivery services. For example, when you order food through an app, your order goes through an analogous processing system with task distribution among different staff members. - -This project will teach you that before you start writing code, you should think through the system architecture, understand how components will interact, and only then proceed to implementation. +# wheres-my-pizza + +## Learning Objectives + +- Message Queue Systems +- RabbitMQ Integration +- Concurrent Programming +- Microservices Architecture + +## Abstract + +In this project, you will build a distributed restaurant order management system. Using Go, you will create several microservices that communicate asynchronously via a RabbitMQ message broker, with order data persisted in a PostgreSQL database. This system will simulate a real-world restaurant workflow, from an order being placed via an API, to it being cooked by a kitchen worker, and finally its status being tracked. This project teaches a fundamental lesson in modern software engineering: think about the architecture first. Before writing a single line of code, you must design how services will interact, how data will flow, and how the system can scale. \ No newline at end of file diff --git a/wheres-my-pizza/QUESTIONS.md b/wheres-my-pizza/QUESTIONS.md index 544799c..39ee30a 100644 --- a/wheres-my-pizza/QUESTIONS.md +++ b/wheres-my-pizza/QUESTIONS.md @@ -1,354 +1,212 @@ -## Project Setup and Compilation -### Does the program compile successfully with `go build -o restaurant-system .`? -- [ ] Yes -- [ ] No - -### Does the code follow gofumpt formatting standards? -- [ ] Yes -- [ ] No - -### Does the program exit with a non-zero status code and clear error message when invalid arguments are provided? -- [ ] Yes -- [ ] No - -### Does the program handle runtime errors gracefully without crashing? -- [ ] Yes -- [ ] No - -### Is the program free of external packages except for pgx/v5 and official AMQP client? -- [ ] Yes -- [ ] No - -## Architecture -### Is the application structured according to microservices architecture principles? -- [ ] Yes -- [ ] No - -### Does the application have clearly separated service responsibilities (Order Service, Kitchen Workers, Tracking Service, Notification Subscriber)? -- [ ] Yes -- [ ] No - -### Does the application implement proper message queue patterns (Work Queue, Publish/Subscribe, Routing)? -- [ ] Yes -- [ ] No - -### Are components properly decoupled through RabbitMQ message broker? -- [ ] Yes -- [ ] No - -### Does the system follow proper separation of concerns between services? -- [ ] Yes -- [ ] No - -## Database Setup and Schema -### Does the program correctly create all required database tables (orders, order_items, order_status_log, workers)? -- [ ] Yes -- [ ] No - -### Does the orders table contain all required fields with proper constraints? -- [ ] Yes -- [ ] No - -### Does the order_items table properly reference orders with foreign key? -- [ ] Yes -- [ ] No - -### Does the order_status_log table track all status changes with timestamps? -- [ ] Yes -- [ ] No - -### Does the workers table manage worker registration and monitoring? -- [ ] Yes -- [ ] No - -## RabbitMQ Configuration -### Does the program correctly set up required exchanges (orders_direct, notifications_fanout)? -- [ ] Yes -- [ ] No - -### Does the program create all required queues with proper durability settings? -- [ ] Yes -- [ ] No - -### Does the program configure priority queues with x-max-priority parameter? -- [ ] Yes -- [ ] No - -### Does the program implement proper routing keys for different order types? -- [ ] Yes -- [ ] No - -### Does the program handle RabbitMQ connection failures and reconnection? -- [ ] Yes -- [ ] No - -## Order Processing -### Does the Order Service accept HTTP POST requests for new orders? -- [ ] Yes -- [ ] No - -### Does the program validate order data according to specified rules? -- [ ] Yes -- [ ] No - -### Does the program calculate total amounts and assign priorities correctly? -- [ ] Yes -- [ ] No - -### Does the program generate proper order numbers in ORD_YYYYMMDD_NNN format? -- [ ] Yes -- [ ] No - -### Does the program store orders in PostgreSQL within transactions? -- [ ] Yes -- [ ] No - -### Does the program publish order messages to RabbitMQ kitchen queue? -- [ ] Yes -- [ ] No - -## Kitchen Worker Implementation -### Do Kitchen Workers consume orders from the correct queues? -- [ ] Yes -- [ ] No - -### Do Kitchen Workers update order status to 'cooking' when processing starts? -- [ ] Yes -- [ ] No - -### Do Kitchen Workers simulate cooking process with configurable duration? -- [ ] Yes -- [ ] No - -### Do Kitchen Workers update order status to 'ready' upon completion? -- [ ] Yes -- [ ] No - -### Do Kitchen Workers acknowledge messages only after successful database updates? -- [ ] Yes -- [ ] No - -## Multiple Workers Support -### Does the program support multiple kitchen workers simultaneously? -- [ ] Yes -- [ ] No - -### Does the program distribute orders evenly among available workers using round-robin? -- [ ] Yes -- [ ] No - -### Does the program register workers in the PostgreSQL workers table? -- [ ] Yes -- [ ] No - -### Does the program track worker status and performance metrics? -- [ ] Yes -- [ ] No - -### Does the program handle worker disconnections gracefully? -- [ ] Yes -- [ ] No - -## Order Status Tracking -### Does the program track all status transitions in order_status_log table? -- [ ] Yes -- [ ] No - -### Does the program validate status transitions to prevent invalid changes? -- [ ] Yes -- [ ] No - -### Does the program provide REST API endpoints for status queries? -- [ ] Yes -- [ ] No - -### Does the program provide order history through API endpoints? -- [ ] Yes -- [ ] No - -### Does the program implement atomic status updates (database + message)? -- [ ] Yes -- [ ] No - -## Notification System -### Does the program use fanout exchange for broadcasting status updates? -- [ ] Yes -- [ ] No - -### Does the program send real-time notifications about status changes? -- [ ] Yes -- [ ] No - -### Does the program support customer-specific notification filtering? -- [ ] Yes -- [ ] No - -### Does the program log all notification deliveries? -- [ ] Yes -- [ ] No - -### Does the program handle notification delivery failures gracefully? -- [ ] Yes -- [ ] No - -## Order Types and Routing -### Does the program support three distinct order types (dine-in, takeout, delivery)? -- [ ] Yes -- [ ] No - -### Does the program enforce different validation rules for each order type? -- [ ] Yes -- [ ] No - -### Does the program implement different cooking times for different order types? -- [ ] Yes -- [ ] No - -### Does the program route orders to specialized worker queues based on type? -- [ ] Yes -- [ ] No - -### Does the program validate table numbers for dine-in and addresses for delivery orders? -- [ ] Yes -- [ ] No - -## Priority Queue Implementation -### Does the program automatically assign priorities based on order characteristics? -- [ ] Yes -- [ ] No - -### Does the program give VIP customers priority 10 (high priority)? -- [ ] Yes -- [ ] No - -### Does the program assign priority 5 for large orders ($50-$100)? -- [ ] Yes -- [ ] No - -### Do workers process orders in priority order (high priority first)? -- [ ] Yes -- [ ] No - -### Does the program log priority assignments in order_status_log? -- [ ] Yes -- [ ] No - -## Configuration and Logging -### Does the program properly read configuration from files and environment variables? -- [ ] Yes -- [ ] No - -### Does the configuration include PostgreSQL connection details? -- [ ] Yes -- [ ] No - -### Does the configuration include RabbitMQ connection details? -- [ ] Yes -- [ ] No - -### Does the program use structured JSON logging throughout the application? -- [ ] Yes -- [ ] No - -### Do logs include contextual information like timestamps, service names, and order numbers? -- [ ] Yes -- [ ] No - -### Does the program log all required events (order lifecycle, worker status, errors)? -- [ ] Yes -- [ ] No - -## System Operation -### Does the program implement graceful shutdown handling for all services? -- [ ] Yes -- [ ] No - -### Does the program display comprehensive usage information with `--help` flag? -- [ ] Yes -- [ ] No - -### Does the program handle database setup with `--setup-db` command? -- [ ] Yes -- [ ] No - -### Does the program handle RabbitMQ setup with `--setup-queues` command? -- [ ] Yes -- [ ] No - -### Does the program support all required operational modes? -- [ ] Yes -- [ ] No - -## API Implementation -### Does the program implement order creation endpoint (POST /orders)? -- [ ] Yes -- [ ] No - -### Does the program implement order status query endpoint (GET /orders/{id}/status)? -- [ ] Yes -- [ ] No - -### Does the program implement order history endpoint (GET /orders/{id}/history)? -- [ ] Yes -- [ ] No - -### Does the program implement worker status monitoring endpoint (GET /workers/status)? -- [ ] Yes -- [ ] No - -### Do all API endpoints return proper HTTP status codes and error messages? -- [ ] Yes -- [ ] No - -## Message Handling -### Does the program properly format order messages according to specification? -- [ ] Yes -- [ ] No - -### Does the program properly format status update messages according to specification? -- [ ] Yes -- [ ] No - -### Does the program ensure message persistence and durability? -- [ ] Yes -- [ ] No - -### Does the program handle message acknowledgments correctly? -- [ ] Yes -- [ ] No - -### Does the program implement proper error handling for failed message deliveries? -- [ ] Yes -- [ ] No - -## Project Defense - -### Can the team explain their microservices architecture decisions? -- [ ] Yes -- [ ] No - -### Can the team explain how they implemented message queue patterns? -- [ ] Yes -- [ ] No - -### Can the team demonstrate understanding of RabbitMQ features used? -- [ ] Yes -- [ ] No - -### Can the team explain their database transaction handling approach? -- [ ] Yes -- [ ] No - -### Can the team explain their priority queue implementation? -- [ ] Yes -- [ ] No - -### Can the team demonstrate the system working with multiple workers? -- [ ] Yes -- [ ] No - -## Detailed Feedback - -### What was great? What you liked the most about the program and the team performance? - +## Project Setup and Compilation +### Does the program compile successfully with `go build -o restaurant-system .`? +- [ ] Yes +- [ ] No + +### Does the code follow gofumpt formatting standards? +- [ ] Yes +- [ ] No + +### Does the program handle runtime errors gracefully without crashing? +- [ ] Yes +- [ ] No + +### Is the program free of external packages except for pgx/v5 and official AMQP client? +- [ ] Yes +- [ ] No + +## Architecture +### Is the application structured according to microservices architecture principles? +- [ ] Yes +- [ ] No + +### Does the application have clearly separated service responsibilities (Order Service, Kitchen Workers, Tracking Service, Notification Subscriber)? +- [ ] Yes +- [ ] No + +### Does the application implement proper message queue patterns (Work Queue, Publish/Subscribe, Routing)? +- [ ] Yes +- [ ] No + +### Are components properly decoupled through RabbitMQ message broker? +- [ ] Yes +- [ ] No + +## Database Schema +### Does the program correctly create all required database tables (orders, order_items, order_status_log, workers)? +- [ ] Yes +- [ ] No + +### Does the orders table contain all required fields with proper constraints? +- [ ] Yes +- [ ] No + +### Does the order_items table properly reference orders with a foreign key? +- [ ] Yes +- [ ] No + +### Does the order_status_log table track all status changes with timestamps? +- [ ] Yes +- [ ] No + +### Does the workers table manage worker registration and monitoring? +- [ ] Yes +- [ ] No + +## RabbitMQ Configuration +### Does the program correctly set up required exchanges (orders_topic, notifications_fanout)? +- [ ] Yes +- [ ] No + +### Does the program create all required queues with proper durability settings? +- [ ] Yes +- [ ] No + +### Does the program configure priority queues with the x-max-priority parameter? +- [ ] Yes +- [ ] No + +### Does the program handle RabbitMQ connection failures and reconnection? +- [ ] Yes +- [ ] No + +## Order Service +### Does the Order Service accept HTTP POST requests for new orders? +- [ ] Yes +- [ ] No + +### Does the program validate order data according to specified rules? +- [ ] Yes +- [ ] No + +### Does the program calculate total amounts and assign priorities correctly? +- [ ] Yes +- [ ] No + +### Does the program generate proper order numbers in ORD_YYYYMMDD_NNN format using UTC time? +- [ ] Yes +- [ ] No + +### Does the program store orders in PostgreSQL within transactions? +- [ ] Yes +- [ ] No + +### Does the program publish order messages to the RabbitMQ kitchen queue? +- [ ] Yes +- [ ] No + +## Kitchen Worker +### Do Kitchen Workers consume orders from the correct queues? +- [ ] Yes +- [ ] No + +### Do Kitchen Workers update order status to 'cooking' when processing starts? +- [ ] Yes +- [ ] No + +### Do Kitchen Workers simulate the cooking process with a configurable duration? +- [ ] Yes +- [ ] No + +### Do Kitchen Workers update order status to 'ready' upon completion? +- [ ] Yes +- [ ] No + +### Do Kitchen Workers acknowledge messages only after successful database updates? +- [ ] Yes +- [ ] No + +### Do specialized workers requeue messages they cannot process? +- [ ] Yes +- [ ] No + +## Multiple Workers & Load Balancing +### Does the program support multiple kitchen workers simultaneously? +- [ ] Yes +- [ ] No + +### Does the program distribute orders among available workers? +- [ ] Yes +- [ ] No + +### Does the program register workers in the PostgreSQL workers table? +- [ ] Yes +- [ ] No + +### Does the program track worker status (online, offline, processing) and performance metrics? +- [ ] Yes +- [ ] No + +### Does the program handle worker disconnections gracefully (graceful shutdown)? +- [ ] Yes +- [ ] No + +## Tracking Service +### Does the Tracking Service provide a REST API endpoint to get the current status of a specific order? +- [ ] Yes +- [ ] No + +### Does the Tracking Service provide a REST API endpoint to get the full status history of an order? +- [ ] Yes +- [ ] No + +### Does the Tracking Service provide a REST API endpoint to get the status of all registered kitchen workers? +- [ ] Yes +- [ ] No + +## Notification Service +### Does the Notification Service use a fanout exchange for broadcasting status updates? +- [ ] Yes +- [ ] No + +### Does the Notification Service consume messages from the notifications_queue? +- [ ] Yes +- [ ] No + +### Does the Notification Service display notifications in a human-readable format? +- [ ] Yes +- [ ] No + +## Logging and Configuration +### Does the program properly read configuration from files? +- [ ] Yes +- [ ] No + +### Does the program use structured JSON logging throughout the application? +- [ ] Yes +- [ ] No + +### Do logs include contextual information like timestamps, service names, and order numbers? +- [ ] Yes +- [ ] No + +## Project Defense +### Can the team explain their microservices architecture decisions? +- [ ] Yes +- [ ] No + +### Can the team explain how they implemented message queue patterns? +- [ ] Yes +- [ ] No + +### Can the team demonstrate understanding of RabbitMQ features used? +- [ ] Yes +- [ ] No + +### Can the team explain their database transaction handling approach? +- [ ] Yes +- [ ] No + +### Can the team explain their priority queue implementation? +- [ ] Yes +- [ ] No + +### Can the team demonstrate the system working with multiple workers? +- [ ] Yes +- [ ] No + +## Detailed Feedback + +### What was great? What you liked the most about the program and the team performance? + ### What could be better? How those improvements could positively impact the outcome? \ No newline at end of file From 1aff25cb8c3e43787e054e903039ae25466ba7f8 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Fri, 27 Jun 2025 19:17:59 +0500 Subject: [PATCH 16/20] fix: PR --- wheres-my-pizza/README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 3a8619d..404783e 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -84,7 +84,7 @@ Your application will consist of four main services, a database, and a message b ```sql create table orders ( - "id" SERIAL PRIMARY KEY, + "id" serial primary key, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "number" text unique not null, @@ -107,9 +107,9 @@ create table orders ( ```sql create table order_items ( - "id" SERIAL PRIMARY KEY, + "id" serial primary key, "created_at" timestamptz not null default now(), - "order_id" integer references orders(id), + "order_id" integer references orders(id), "name" text not null, "quantity" integer not null, "price" decimal(8,2) not null @@ -122,9 +122,9 @@ create table order_items ( ```sql create table order_status_log ( - "id" SERIAL PRIMARY KEY, + "id" serial primary key, "created_at" timestamptz not null default now(), - "order_id" integer references orders(id), + "order_id" integer references orders(id), "status" text, "changed_by" text, "changed_at" timestamptz default current_timestamp, @@ -138,7 +138,7 @@ create table order_status_log ( ```sql create table workers ( - "id" SERIAL PRIMARY KEY, + "id" serial primary key, "created_at" timestamptz not null default now(), "name" text unique not null, "type" text not null, @@ -452,11 +452,11 @@ Create all required tables as specified in the Database Schema section. Each ser The system assigns a priority level (`10`, `5`, `1`) to each order. If an order matches multiple criteria, the highest applicable priority is assigned. This priority level is used in the routing key. -| Priority | Criteria | -| :------- | :-------------------------------------------------------------------- | -| `10` | Order total amount is greater than $100. | -| `5` | Order total amount is between $50 and $100. | -| `1` | All other standard orders. | +| Priority | Criteria | +| :------- | :------------------------------------------ | +| `10` | Order total amount is greater than $100. | +| `5` | Order total amount is between $50 and $100. | +| `1` | All other standard orders. | #### Validation Rules & Edge Cases From 471cdb8d44bc9d97285721bc23674b47c69ce8ff Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Fri, 27 Jun 2025 19:27:10 +0500 Subject: [PATCH 17/20] fix: style --- wheres-my-pizza/README.md | 178 +++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 404783e..4ddb87d 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -242,13 +242,13 @@ All services must implement structured logging in JSON format. **Mandatory Core Fields:** The following keys must always be present in every log entry: -* `timestamp`: The time the log entry was created. -* `level`: The log level (e.g., INFO, ERROR). -* `service`: The name of the service emitting the log (e.g., `order-service`, `kitchen-worker`). -* `action`: A concise, machine-readable string describing the event (e.g., `order_received`, `db_error`). -* `message`: A human-readable description of the event. -* `hostname`: The hostname or unique identifier of the module emitting the log. -* `request_id`: A unique identifier for correlating requests/operations across multiple services. +- `timestamp`: The time the log entry was created. +- `level`: The log level (e.g., INFO, ERROR). +- `service`: The name of the service emitting the log (e.g., `order-service`, `kitchen-worker`). +- `action`: A concise, machine-readable string describing the event (e.g., `order_received`, `db_error`). +- `message`: A human-readable description of the event. +- `hostname`: The hostname or unique identifier of the module emitting the log. +- `request_id`: A unique identifier for correlating requests/operations across multiple services. **Format Example:** ```json @@ -293,15 +293,15 @@ For `ERROR` level logs, an `error` object must be included with the following st ### Log Levels Usage -* **ERROR:** Database connection failures, RabbitMQ connection drops, order validation failures, message publish/consume errors, system crashes. -* **WARN:** Message retry attempts, database query timeout warnings, configuration issues, order processing delays, worker disconnection/reconnection events. -* **INFO:** Order lifecycle events (received, cooking started, completed), worker status changes, service startup/shutdown, normal business operations, performance milestones. -* **DEBUG:** Detailed message content, database query execution details, internal processing steps, performance metrics, development and troubleshooting information. +- **ERROR:** Database connection failures, RabbitMQ connection drops, order validation failures, message publish/consume errors, system crashes. +- **WARN:** Message retry attempts, database query timeout warnings, configuration issues, order processing delays, worker disconnection/reconnection events. +- **INFO:** Order lifecycle events (received, cooking started, completed), worker status changes, service startup/shutdown, normal business operations, performance milestones. +- **DEBUG:** Detailed message content, database query execution details, internal processing steps, performance metrics, development and troubleshooting information. ### Log Location and Format Rules: -* All logs must be emitted as single-line JSON (no pretty printing) to `stdout`. This is crucial for containerized environments where log collectors typically consume `stdout`. -* Logs must not contain Personally Identifiable Information (PII), such as `customer_address`, payment information, or other sensitive data. -* All log messages must be UTF-8 encoded, and newlines within log fields must be properly escaped. +- All logs must be emitted as single-line JSON (no pretty printing) to `stdout`. This is crucial for containerized environments where log collectors typically consume `stdout`. +- Logs must not contain Personally Identifiable Information (PII), such as `customer_address`, payment information, or other sensitive data. +- All log messages must be UTF-8 encoded, and newlines within log fields must be properly escaped. ### Required Log Events: @@ -407,8 +407,8 @@ Create all required tables as specified in the Database Schema section. Each ser #### API Endpoint - * **Endpoint:** `POST /orders` - * **Content-Type:** `application/json` +- **Endpoint:** `POST /orders` +- **Content-Type:** `application/json` #### Incoming Request Format @@ -433,9 +433,9 @@ Create all required tables as specified in the Database Schema section. Each ser 6. **Store order** in PostgreSQL `orders` and `order_items` tables within a transaction. 7. **Log initial status** to `order_status_log`. 8. **Publish `Order Message` to RabbitMQ**: - * **Exchange:** `orders_topic`. - * **Routing Key:** `kitchen.{order_type}.{priority}` (e.g., `kitchen.takeout.1`). - * **Message Properties:** Set `delivery_mode: 2` (persistent). For priority queueing, map the priority level (`10`/`5`/`1`) to a numeric value and set it in the `priority` message property. + - **Exchange:** `orders_topic`. + - **Routing Key:** `kitchen.{order_type}.{priority}` (e.g., `kitchen.takeout.1`). + - **Message Properties:** Set `delivery_mode: 2` (persistent). For priority queueing, map the priority level (`10`/`5`/`1`) to a numeric value and set it in the `priority` message property. 9. **Return HTTP JSON response.** #### Outgoing Response Format @@ -460,19 +460,19 @@ The system assigns a priority level (`10`, `5`, `1`) to each order. If an order #### Validation Rules & Edge Cases - * `customer_name`: required, 1-100 characters, no special characters except spaces, hyphens, apostrophes. - * `order_type`: required, must be one of: 'dine_in', 'takeout', 'delivery'. - * `items`: required array, minimum 1 item, maximum 20 items per order. - * `item.name`: required, 1-50 characters. - * `item.quantity`: required, integer, 1-10 per item. - * `item.price`: required, decimal, 0.01-999.99. - * `table_number`: required for `dine_in` orders, 1-100. - * `delivery_address`: required for `delivery` orders, minimum 10 characters. - * **Conflicting Fields**: - * `dine_in` orders must NOT include `delivery_address`. - * `delivery` orders must NOT include `table_number`. - * **Duplicate Items**: Reject order if `items` contains duplicate entries by `name` that would violate the `quantity` rule (e.g., two "Margherita Pizza" entries summing to more than 10 quantity). - * **Database Transactions**: All database operations for order creation must be transactional. +- `customer_name`: required, 1-100 characters, no special characters except spaces, hyphens, apostrophes. +- `order_type`: required, must be one of: 'dine_in', 'takeout', 'delivery'. +- `items`: required array, minimum 1 item, maximum 20 items per order. +- `item.name`: required, 1-50 characters. +- `item.quantity`: required, integer, 1-10 per item. +- `item.price`: required, decimal, 0.01-999.99. +- `table_number`: required for `dine_in` orders, 1-100. +- `delivery_address`: required for `delivery` orders, minimum 10 characters. +- **Conflicting Fields**: + - `dine_in` orders must NOT include `delivery_address`. + - `delivery` orders must NOT include `table_number`. +- **Duplicate Items**: Reject order if `items` contains duplicate entries by `name` that would violate the `quantity` rule (e.g., two "Margherita Pizza" entries summing to more than 10 quantity). +- **Database Transactions**: All database operations for order creation must be transactional. #### Log Requirements @@ -480,9 +480,9 @@ Use structured logging format as defined in the [Logging Format](#logging-format #### Flags - * `--mode`: Service mode (required: `order-service`) - * `--port`: HTTP port for REST API (default: 3000) - * `--max-concurrent`: Maximum concurrent orders (default: 50). +- `--mode`: Service mode (required: `order-service`) +- `--port`: HTTP port for REST API (default: 3000) +- `--max-concurrent`: Maximum concurrent orders (default: 50). #### Worked Example @@ -516,41 +516,41 @@ curl -X POST http://localhost:3000/orders \ #### Order Type Processing - * **dine_in:** - * Requires `table_number`. - * Shorter cooking time (8 seconds). - * **takeout:** - * Standard processing. - * Medium cooking time (10 seconds). - * **delivery:** - * Requires `delivery_address`. - * Longer cooking time (12 seconds). +- **dine_in:** + - Requires `table_number`. + - Shorter cooking time (8 seconds). +- **takeout:** + - Standard processing. + - Medium cooking time (10 seconds). +- **delivery:** + - Requires `delivery_address`. + - Longer cooking time (12 seconds). #### Worker Specialization - * Workers can be configured to only accept certain types of orders using the `--order-types` flag. If a worker receives an order it cannot process, it must negatively acknowledge the message (`basic.nack`) and requeue it so another worker can pick it up. +- Workers can be configured to only accept certain types of orders using the `--order-types` flag. If a worker receives an order it cannot process, it must negatively acknowledge the message (`basic.nack`) and requeue it so another worker can pick it up. ### Kitchen Worker #### Incoming Message Format - * **Queue:** `kitchen_queue` (or specialized queues like `kitchen_delivery_queue`) - * **Format:** See [Order Message](#order-message) section +- **Queue:** `kitchen_queue` (or specialized queues like `kitchen_delivery_queue`) +- **Format:** See [Order Message](#order-message) section #### Processing Logic 1. **Consume Message:** Consume an order message from a bound kitchen queue. 2. **Process Order:** - * Update the order's `status` to `cooking` in the `orders` table. - * Log the status change in the `order_status_log` table. - * Publish a `status_update` message to the `notifications_fanout` exchange. + - Update the order's `status` to `cooking` in the `orders` table. + - Log the status change in the `order_status_log` table. + - Publish a `status_update` message to the `notifications_fanout` exchange. 3. **Simulate Cooking:** Simulate the cooking process with a configurable duration. 4. **Update Status to 'ready':** - * Update the order's `status` to `ready` in the `orders` table. - * Update the `completed_at` timestamp. - * Increment the `orders_processed` counter for the worker in the `workers` table. - * Log the status change in the `order_status_log` table. - * Publish a `status_update` message to the `notifications_fanout` exchange. + - Update the order's `status` to `ready` in the `orders` table. + - Update the `completed_at` timestamp. + - Increment the `orders_processed` counter for the worker in the `workers` table. + - Log the status change in the `order_status_log` table. + - Publish a `status_update` message to the `notifications_fanout` exchange. 5. **Acknowledge Receipt:** Acknowledge the message to RabbitMQ (`basic.ack`). 6. **Handle Processing Failure:** If any step in processing fails (e.g., database error, message publish error), reject the message with `basic.nack(requeue=true)` to return it to the queue for redelivery. @@ -560,9 +560,9 @@ Use structured logging format as defined in the [Logging Format](#logging-format #### Relevant Flags - * `--port`: HTTP port for REST API (default: 3001) - * `--worker-name`: Unique name for the worker (required). - * `--order-types`: Comma-separated list of order types the worker can process (e.g., `dine_in,takeout`). +- `--port`: HTTP port for REST API (default: 3001) +- `--worker-name`: Unique name for the worker (required). +- `--order-types`: Comma-separated list of order types the worker can process (e.g., `dine_in,takeout`). #### Worked Example @@ -584,28 +584,28 @@ $ curl http://localhost:3001/workers/status #### Worker Management - * **Registration:** When a `kitchen-worker` starts, it should register itself in the `workers` table in the database. - * **Status Tracking:** The worker's status (`online`, `offline`, `processing`) and `last_seen` should be updated regularly. - * `online` indicates the worker is running and ready to receive orders. - * `processing` should be set when the worker is actively handling an order. - * `offline` should be set during graceful shutdown. +- **Registration:** When a `kitchen-worker` starts, it should register itself in the `workers` table in the database. +- **Status Tracking:** The worker's status (`online`, `offline`, `processing`) and `last_seen` should be updated regularly. + - `online` indicates the worker is running and ready to receive orders. + - `processing` should be set when the worker is actively handling an order. + - `offline` should be set during graceful shutdown. #### Heartbeat & Offline Detection - * Each `kitchen-worker` sends a heartbeat every `--heartbeat-interval` (default 30 seconds) by executing an `UPDATE` query on the `workers` table: `UPDATE workers SET last_seen = now(), status='online' WHERE name = `. This indicates the worker is alive and active. - * The `tracking-service` (or a dedicated monitoring component) should periodically check the `workers` table. A worker is considered `offline` if `now() - last_seen` is greater than `2 * --heartbeat-interval`. +- Each `kitchen-worker` sends a heartbeat every `--heartbeat-interval` (default 30 seconds) by executing an `UPDATE` query on the `workers` table: `UPDATE workers SET last_seen = now(), status='online' WHERE name = `. This indicates the worker is alive and active. +- The `tracking-service` (or a dedicated monitoring component) should periodically check the `workers` table. A worker is considered `offline` if `now() - last_seen` is greater than `2 * --heartbeat-interval`. #### Load Balancing - * **Round-Robin:** By default, RabbitMQ will distribute orders to available workers in a round-robin fashion. - * **Priority Queues:** If queues are configured with `x-max-priority`, RabbitMQ delivers higher-priority messages before lower-priority regardless of round-robin. - * **Worker Specialization:** Workers can be configured to only accept certain types of orders using the `--order-types` flag. +- **Round-Robin:** By default, RabbitMQ will distribute orders to available workers in a round-robin fashion. +- **Priority Queues:** If queues are configured with `x-max-priority`, RabbitMQ delivers higher-priority messages before lower-priority regardless of round-robin. +- **Worker Specialization:** Workers can be configured to only accept certain types of orders using the `--order-types` flag. #### Redelivery Algorithm - * **Prefetch Count:** Each `kitchen-worker` should configure its RabbitMQ consumer with `basic.qos(prefetch_count=N)` (where N is the `--prefetch` flag, default 1). This limits the number of unacknowledged messages a worker can receive at a time. - * **Clean Disconnection:** If a worker disconnects cleanly (e.g., graceful shutdown), it should `basic.nack(requeue=true)` any outstanding unacknowledged deliveries. This ensures messages are returned to the queue for other workers. - * **Unclean Disconnection/Crash:** If a worker crashes or disconnects uncleanly, RabbitMQ will automatically re-queue any unacknowledged messages after a timeout, making them available to other workers. +- **Prefetch Count:** Each `kitchen-worker` should configure its RabbitMQ consumer with `basic.qos(prefetch_count=N)` (where N is the `--prefetch` flag, default 1). This limits the number of unacknowledged messages a worker can receive at a time. +- **Clean Disconnection:** If a worker disconnects cleanly (e.g., graceful shutdown), it should `basic.nack(requeue=true)` any outstanding unacknowledged deliveries. This ensures messages are returned to the queue for other workers. +- **Unclean Disconnection/Crash:** If a worker crashes or disconnects uncleanly, RabbitMQ will automatically re-queue any unacknowledged messages after a timeout, making them available to other workers. #### Log Requirements @@ -613,16 +613,16 @@ Use structured logging format as defined in the [Logging Format](#logging-format #### Config / Flags - * `--worker-name`: Unique worker name (required). - * `--order-types`: Comma-separated order types to process. - * `--heartbeat-interval`: Interval in seconds for sending worker heartbeats (default: 30). - * `--prefetch`: RabbitMQ prefetch count for the worker (default: 1). +- `--worker-name`: Unique worker name (required). +- `--order-types`: Comma-separated order types to process. +- `--heartbeat-interval`: Interval in seconds for sending worker heartbeats (default: 30). +- `--prefetch`: RabbitMQ prefetch count for the worker (default: 1). #### Validation Rules & Edge Cases - * **Duplicate Worker Name**: If the `INSERT` into `workers` violates name uniqueness, log `ERROR` and exit with status code `1`. - * **Graceful Shutdown**: Upon receiving a shutdown signal (e.g., `SIGINT`, `SIGTERM`), a worker should immediately stop accepting new orders from the queue. It must then finish processing any in-flight order, set its status to `offline` in the `workers` table, attempt to `basic.nack(requeue=true)` any unacknowledged messages, and then gracefully exit. This ensures that no work is lost and the system state remains consistent. - * **Database Unreachable**: Handle scenarios where the database is temporarily unreachable during heartbeat updates or registration. Implement retry mechanisms (e.g., three attempts with exponential back-off starting at 1 second, capped at 30 seconds) for database write operations. +- **Duplicate Worker Name**: If the `INSERT` into `workers` violates name uniqueness, log `ERROR` and exit with status code `1`. +- **Graceful Shutdown**: Upon receiving a shutdown signal (e.g., `SIGINT`, `SIGTERM`), a worker should immediately stop accepting new orders from the queue. It must then finish processing any in-flight order, set its status to `offline` in the `workers` table, attempt to `basic.nack(requeue=true)` any unacknowledged messages, and then gracefully exit. This ensures that no work is lost and the system state remains consistent. +- **Database Unreachable**: Handle scenarios where the database is temporarily unreachable during heartbeat updates or registration. Implement retry mechanisms (e.g., three attempts with exponential back-off starting at 1 second, capped at 30 seconds) for database write operations. ### Tracking Service @@ -630,10 +630,10 @@ This service provides HTTP API endpoints for querying order status, history, and #### API Endpoints - * **`GET /orders/{order_number}/status`:** +- **`GET /orders/{order_number}/status`:** - * **Purpose:** To get the current status of a specific order. - * **Response Format:** + - **Purpose:** To get the current status of a specific order. + - **Response Format:** ```json { "order_number": "ORD_20241216_001", @@ -644,10 +644,10 @@ This service provides HTTP API endpoints for querying order status, history, and } ``` - * **`GET /orders/{order_number}/history`:** +- **`GET /orders/{order_number}/history`:** - * **Purpose:** To get the full status history of an order. - * **Response Format:** + - **Purpose:** To get the full status history of an order. + - **Response Format:** ```json [ {"status": "received", "timestamp": "2024-12-16T10:30:00Z", "changed_by": "order-service"}, @@ -655,10 +655,10 @@ This service provides HTTP API endpoints for querying order status, history, and ] ``` - * **`GET /workers/status`:** +- **`GET /workers/status`:** - * **Purpose:** To get the status of all registered kitchen workers. - * **Response Format:** + - **Purpose:** To get the status of all registered kitchen workers. + - **Response Format:** ```json [ {"worker_name": "chef_mario", "status": "online", "orders_processed": 5, "last_seen": "2024-12-16T10:35:00Z"}, @@ -668,8 +668,8 @@ This service provides HTTP API endpoints for querying order status, history, and #### Flags - * `--mode`: Service mode (required: `tracking-service`) - * `--port`: HTTP port for REST API (default: 3002) +- `--mode`: Service mode (required: `tracking-service`) +- `--port`: HTTP port for REST API (default: 3002) ### Notification Service From 70dcddad45694c47ad957bddfe962b24ac91be7b Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Fri, 27 Jun 2025 19:27:59 +0500 Subject: [PATCH 18/20] fix: style --- wheres-my-pizza/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 4ddb87d..319a443 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -611,7 +611,7 @@ $ curl http://localhost:3001/workers/status Use structured logging format as defined in the [Logging Format](#logging-format) section. -#### Config / Flags +#### Flags - `--worker-name`: Unique worker name (required). - `--order-types`: Comma-separated order types to process. From babc8a378ee6bcea2f3166210de4e199ff391970 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Fri, 27 Jun 2025 19:40:13 +0500 Subject: [PATCH 19/20] fix: PR --- wheres-my-pizza/README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 319a443..84a0a16 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -558,9 +558,8 @@ curl -X POST http://localhost:3000/orders \ Use structured logging format as defined in the [Logging Format](#logging-format) section. -#### Relevant Flags +#### Flags -- `--port`: HTTP port for REST API (default: 3001) - `--worker-name`: Unique name for the worker (required). - `--order-types`: Comma-separated list of order types the worker can process (e.g., `dine_in,takeout`). @@ -571,13 +570,6 @@ Use structured logging format as defined in the [Logging Format](#logging-format $ ./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --order-types="dine_in" & $ ./restaurant-system --mode=kitchen-worker --worker-name="chef_luigi" --order-types="delivery" & $ ./restaurant-system --mode=kitchen-worker --worker-name="chef_anna" & - -# Monitor worker status -$ curl http://localhost:3001/workers/status -[ - {"worker_name": "chef_mario", "status": "online", "orders_processed": 5, "last_seen": "2024-12-16T10:35:00Z"}, - {"worker_name": "chef_luigi", "status": "online", "orders_processed": 3, "last_seen": "2024-12-16T10:35:01Z"} -] ``` ### Multiple Workers & Load Balancing From 220f693134fb3a3e4cce2ed367e972f1269ce470 Mon Sep 17 00:00:00 2001 From: ymoldabe Date: Tue, 1 Jul 2025 03:09:21 -0500 Subject: [PATCH 20/20] fix: PR --- wheres-my-pizza/QUESTIONS.md | 148 +----- wheres-my-pizza/README.md | 982 ++++++++++++++++++----------------- 2 files changed, 530 insertions(+), 600 deletions(-) diff --git a/wheres-my-pizza/QUESTIONS.md b/wheres-my-pizza/QUESTIONS.md index 39ee30a..cc82af1 100644 --- a/wheres-my-pizza/QUESTIONS.md +++ b/wheres-my-pizza/QUESTIONS.md @@ -15,193 +15,93 @@ - [ ] Yes - [ ] No -## Architecture -### Is the application structured according to microservices architecture principles? +## Program functionality +### The Order Service accepts HTTP POST requests on /orders endpoint and validates input according to specified rules. - [ ] Yes - [ ] No -### Does the application have clearly separated service responsibilities (Order Service, Kitchen Workers, Tracking Service, Notification Subscriber)? +### The Order Service generates unique order numbers in format ORD_YYYYMMDD_NNN that reset daily. - [ ] Yes - [ ] No -### Does the application implement proper message queue patterns (Work Queue, Publish/Subscribe, Routing)? +### The Order Service calculates priority based on total amount (10 for >$100, 5 for $50-$100, 1 for others). - [ ] Yes - [ ] No -### Are components properly decoupled through RabbitMQ message broker? +### The Order Service stores orders, order items, and status logs in database within a single transaction. - [ ] Yes - [ ] No -## Database Schema -### Does the program correctly create all required database tables (orders, order_items, order_status_log, workers)? +### The Order Service publishes order messages to RabbitMQ with correct routing keys and message properties. - [ ] Yes - [ ] No -### Does the orders table contain all required fields with proper constraints? +### The Kitchen Worker registers itself in the workers table and handles duplicate worker name scenarios correctly. - [ ] Yes - [ ] No -### Does the order_items table properly reference orders with a foreign key? +### The Kitchen Worker consumes messages from kitchen queues and processes orders according to specialization. - [ ] Yes - [ ] No -### Does the order_status_log table track all status changes with timestamps? +### The Kitchen Worker updates order status to 'cooking', simulates cooking time, then updates to 'ready'. - [ ] Yes - [ ] No -### Does the workers table manage worker registration and monitoring? +### The Kitchen Worker publishes status update messages to notifications_fanout exchange. - [ ] Yes - [ ] No -## RabbitMQ Configuration -### Does the program correctly set up required exchanges (orders_topic, notifications_fanout)? +### The Kitchen Worker handles graceful shutdown by stopping consumption and updating status to 'offline'. - [ ] Yes - [ ] No -### Does the program create all required queues with proper durability settings? +### The Tracking Service provides read-only HTTP API for order status, history, and worker status. - [ ] Yes - [ ] No -### Does the program configure priority queues with the x-max-priority parameter? +### The Tracking Service returns proper HTTP status codes (404 for not found, 500 for server errors). - [ ] Yes - [ ] No -### Does the program handle RabbitMQ connection failures and reconnection? +### The Notification Service subscribes to status updates and displays human-readable notifications. - [ ] Yes - [ ] No -## Order Service -### Does the Order Service accept HTTP POST requests for new orders? +### All services implement structured JSON logging with required fields (timestamp, level, service, action, message, hostname, request_id). - [ ] Yes - [ ] No -### Does the program validate order data according to specified rules? +### All services handle RabbitMQ reconnection scenarios and implement proper error handling. - [ ] Yes - [ ] No -### Does the program calculate total amounts and assign priorities correctly? +### All database operations are transactional where appropriate and handle connection failures. - [ ] Yes - [ ] No -### Does the program generate proper order numbers in ORD_YYYYMMDD_NNN format using UTC time? +### The system implements proper message acknowledgment (basic.ack, basic.nack) patterns. - [ ] Yes - [ ] No -### Does the program store orders in PostgreSQL within transactions? +### All services can be configured via YAML configuration file for database and RabbitMQ settings. - [ ] Yes - [ ] No -### Does the program publish order messages to the RabbitMQ kitchen queue? +## Project presentation and code defense +### Can the team clearly explain their microservices architecture, message flow, and design choices during the presentation? - [ ] Yes - [ ] No -## Kitchen Worker -### Do Kitchen Workers consume orders from the correct queues? +### Can the team effectively demonstrate the complete order lifecycle from creation to completion? - [ ] Yes - [ ] No -### Do Kitchen Workers update order status to 'cooking' when processing starts? +### Can the team explain how they handle concurrent processing, message ordering, and failure scenarios? - [ ] Yes - [ ] No -### Do Kitchen Workers simulate the cooking process with a configurable duration? -- [ ] Yes -- [ ] No - -### Do Kitchen Workers update order status to 'ready' upon completion? -- [ ] Yes -- [ ] No - -### Do Kitchen Workers acknowledge messages only after successful database updates? -- [ ] Yes -- [ ] No - -### Do specialized workers requeue messages they cannot process? -- [ ] Yes -- [ ] No - -## Multiple Workers & Load Balancing -### Does the program support multiple kitchen workers simultaneously? -- [ ] Yes -- [ ] No - -### Does the program distribute orders among available workers? -- [ ] Yes -- [ ] No - -### Does the program register workers in the PostgreSQL workers table? -- [ ] Yes -- [ ] No - -### Does the program track worker status (online, offline, processing) and performance metrics? -- [ ] Yes -- [ ] No - -### Does the program handle worker disconnections gracefully (graceful shutdown)? -- [ ] Yes -- [ ] No - -## Tracking Service -### Does the Tracking Service provide a REST API endpoint to get the current status of a specific order? -- [ ] Yes -- [ ] No - -### Does the Tracking Service provide a REST API endpoint to get the full status history of an order? -- [ ] Yes -- [ ] No - -### Does the Tracking Service provide a REST API endpoint to get the status of all registered kitchen workers? -- [ ] Yes -- [ ] No - -## Notification Service -### Does the Notification Service use a fanout exchange for broadcasting status updates? -- [ ] Yes -- [ ] No - -### Does the Notification Service consume messages from the notifications_queue? -- [ ] Yes -- [ ] No - -### Does the Notification Service display notifications in a human-readable format? -- [ ] Yes -- [ ] No - -## Logging and Configuration -### Does the program properly read configuration from files? -- [ ] Yes -- [ ] No - -### Does the program use structured JSON logging throughout the application? -- [ ] Yes -- [ ] No - -### Do logs include contextual information like timestamps, service names, and order numbers? -- [ ] Yes -- [ ] No - -## Project Defense -### Can the team explain their microservices architecture decisions? -- [ ] Yes -- [ ] No - -### Can the team explain how they implemented message queue patterns? -- [ ] Yes -- [ ] No - -### Can the team demonstrate understanding of RabbitMQ features used? -- [ ] Yes -- [ ] No - -### Can the team explain their database transaction handling approach? -- [ ] Yes -- [ ] No - -### Can the team explain their priority queue implementation? -- [ ] Yes -- [ ] No - -### Can the team demonstrate the system working with multiple workers? +### Can the team show how different worker specializations and load balancing work in practice? - [ ] Yes - [ ] No diff --git a/wheres-my-pizza/README.md b/wheres-my-pizza/README.md index 84a0a16..2b1b4e8 100644 --- a/wheres-my-pizza/README.md +++ b/wheres-my-pizza/README.md @@ -28,68 +28,153 @@ In this project, you will build the core of such a system. You will learn how to A smart way to solve this type of problem is using message queue patterns. This approach views the system as a set of interacting services, where each service processes a specific type of message and passes the result further down the chain. **Work Queue Pattern** + - One producer sends tasks to a queue - Multiple consumers wait for the task to arrive, but only one receives it - Each task is processed by exactly one consumer - Provides load distribution among workers **Publish/Subscribe Pattern** + - One publisher sends messages to all subscribers - Multiple consumers receive copies of messages - Used for notifications and state synchronization **Routing Pattern** + - Messages are routed based on routing key - Allows creating complex processing schemes - Provides flexibility in defining recipients +## General Criteria + +- Your code MUST be written in accordance with [gofumpt](https://github.com/mvdan/gofumpt). If not, you will automatically receive a score of `0`. +- Your program MUST compile successfully. +- Your program MUST NOT crash unexpectedly (any panics: `nil-pointer dereference`, `index out of range`, etc.). If this happens, you will receive `0` points during defense. +- Only built-in Go packages, `pgx/v5` and the official AMQP client (`github.com/rabbitmq/amqp091-go`) are allowed. If other packages are used, you will receive a score of `0`. +- RabbitMQ server MUST be running and available for connection. +- PostgreSQL database MUST be running and accessible for all services +- All RabbitMQ connections must handle reconnection scenarios +- Implement proper graceful shutdown for all services +- All database operations must be transactional where appropriate +- The project MUST compile with the following command in the project root directory: + +```sh +$ go build -o restaurant-system . +``` + +### Logging Format + +All services must implement structured logging in JSON format and write to `stdout`. + +- **Core Fields (Mandatory):** `timestamp`, `level`, `service`, `action`, `message`, `hostname`, `request_id`. +- **Error Object:** For `ERROR` level logs, an `error` object with `msg`, and `stack` must be included. + +#### Field Descriptions + +| Key | Type | Description | +| :----------- | :----- | :----------------------------------------------------------------------------------------- | +| `timestamp` | string | Time the log entry was created, in ISO 8601 format. | +| `level` | string | Severity level of the event. Valid values: `INFO`, `DEBUG`, `ERROR`. | +| `service` | string | Name of the service that generated the log (e.g., `order-service`, `kitchen-worker`). | +| `action` | string | Short, machine-readable event name (e.g., `order_created`, `db_error`, `retry_attempted`). | +| `message` | string | Human-readable message describing the event. | +| `hostname` | string | Hostname or container ID of the service emitting the log. | +| `request_id` | string | Correlation ID for tracing a single operation across multiple services. | +| `error` | object | _(Only for `ERROR` logs)_ Contains structured error information. | +| ├─ `msg` | string | Error message explaining the cause. | +| └─ `stack` | string | Stack trace or error traceback for debugging purposes. | + +### Configuration + +Services should be configurable via a YAML file (`config.yaml`) for database and RabbitMQ connection details. + +```yaml +# Database Configuration +database: + host: localhost + port: 5432 + user: restaurant_user + password: restaurant_pass + database: restaurant_db + +# RabbitMQ Configuration +rabbitmq: + host: localhost + port: 5672 + user: guest + password: guest +``` + +## Resources + +- [RabbitMQ Documentation](https://www.rabbitmq.com/documentation.html) +- [RabbitMQ Docker Image](https://hub.docker.com/_/rabbitmq) +- [Go AMQP Client](https://github.com/rabbitmq/amqp091-go) +- [PostgreSQL Go Driver (pgx)](https://github.com/jackc/pgx/v5) + ## System Architecture Overview Your application will consist of four main services, a database, and a message broker. They interact as follows: ``` - +------------------+ - | PostgreSQL DB | - | (Order Storage) | - +--+-------------+-+ - ^ ^ - (Writes & Reads) | | (Writes & Reads) - | | -+------------+ +----------+ v v +---------------+ -| HTTP Client|------->| Order | | Kitchen | -| (e.g. curl)| | Service | | Service | -+------------+ +----------+ +--+------------+ - | ^ - | (Publishes New Order) | (Publishes Status Update) - v | - +-----+-------------------------+-----+ - | | - | RabbitMQ Message Broker | - | | - +-------------------------------------+ - | | - | (Status Updates) | (Status Updates) - v v - +-----+-----------+ +-----+-----------+ - | Notification | | Tracking | - | Subscriber | | Service | - +-----------------+ +-----------------+ + +--------------------------------------------+ + | PostgreSQL DB | + | (Order Storage) | + +--+-------------+---------------------------+ + ^ ^ | + (Writes & Reads) | | (Writes & Reads) | + v v | ++------------+ +-----------+ +---------------+ | +| HTTP Client|------->| Order | | Kitchen | | +| (e.g. curl)| | Service | | Service | | ++------------+ +---------- + +-+-------------+ | + | ^ | + (Publishes New Order) (Publishes Status Update) | + v | | + +-----+-------------------------+---------+ | + | | | + | RabbitMQ Message Broker | | + | | | + +-----------------------------------------+ | + | | + | (Status Updates) | (Reads) + v v + +-----+-----------+ +-----+------------------+ + | Notification | | Tracking | + | Subscriber | | Service | + +-----------------+ +------------------------+ ``` -## Database Schema +## Feature: Order Service + +### Context -### Orders Table -**Purpose**: Primary storage for all restaurant orders with complete order information -**Used by**: Order Service (insert), Kitchen Workers (status updates), Tracking Service (queries) +The Order Service is the public-facing entry point of the restaurant system. Its primary responsibility is to receive new orders from customers via an HTTP API, validate them, store them in the database, and publish them to a message queue for the kitchen staff to process. It acts as the gatekeeper, ensuring all incoming data is correct and formatted before entering the system. + +### Migrations + +This service interacts with the following database tables. It is responsible for inserting new records into them. + +- **`orders` Table**: The primary table for storing all order details. + +- **Possible statuses for `orders`:** + +| Status | Description | +| ----------- | --------------------------------------------------- | +| `received` | Order has been received and is awaiting processing. | +| `cooking` | Order is currently being prepared in the kitchen. | +| `ready` | Order is ready for pickup or delivery. | +| `completed` | Order has been successfully delivered or picked up. | +| `cancelled` | Order has been cancelled. | ```sql -create table orders ( +create table "orders" ( "id" serial primary key, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "number" text unique not null, "customer_name" text not null, - "customer_type" text default 'regular', "type" text not null check (type in ('dine_in', 'takeout', 'delivery')), "table_number" integer, "delivery_address" text, @@ -101,9 +186,7 @@ create table orders ( ); ``` -### Order Items Table -**Purpose**: Stores individual items within each order for detailed order composition -**Used by**: Order Service (insert items when creating orders), API queries for order details +- **`order_items` Table**: Stores the individual items associated with each order. ```sql create table order_items ( @@ -116,9 +199,17 @@ create table order_items ( ); ``` -### Order Status Log Table -**Purpose**: Audit trail for all status changes throughout order lifecycle -**Used by**: All services (insert status changes), Tracking Service (status history queries) +- **`order_status_log` Table**: Creates an audit trail for an order's lifecycle, starting with the `received` status. + +- **Possible statuses for `order_status_log`:** + +| Status | Description | +| ----------- | ------------------------------------------------------- | +| `received` | The order has been received and is awaiting processing. | +| `cooking` | The order is currently being prepared in the kitchen. | +| `ready` | The order is ready for pickup or delivery. | +| `completed` | The order has been successfully delivered or picked up. | +| `cancelled` | The order has been cancelled. | ```sql create table order_status_log ( @@ -132,573 +223,512 @@ create table order_status_log ( ); ``` -### Workers Table -**Purpose**: Registry and monitoring of all kitchen workers and their current status -**Used by**: Kitchen Workers (registration and heartbeat), Tracking Service (worker monitoring) - -```sql -create table workers ( - "id" serial primary key, - "created_at" timestamptz not null default now(), - "name" text unique not null, - "type" text not null, - "status" text default 'online', - "last_seen" timestamptz default current_timestamp, - "orders_processed" integer default 0 -); -``` - -## RabbitMQ Configuration - -### Exchanges and Queues Setup +### API -**Exchange Types Explained:** - -**Topic Exchange (`orders_topic`)**: Routes messages to queues based on pattern matching with routing keys. Used for flexible order routing where different order types and priorities can be routed to specialized queues. - -**Fanout Exchange (`notifications_fanout`)**: Broadcasts all messages to every queue bound to it, ignoring routing keys. Used for notifications where all subscribers need to receive status updates regardless of their specific interests. +#### Create new customer order +```http +POST /orders ``` -Exchanges: -├── orders_topic (type: topic, durable: true) -│ └── Routing Keys (examples): -│ ├── kitchen.dine_in.10 -│ ├── kitchen.takeout.5 -│ └── kitchen.delivery.1 -│ -└── notifications_fanout (type: fanout, durable: true) - └── Broadcasts to all subscribers - -Queues: -├── kitchen_queue (durable: true, x-max-priority: 10) -│ └── Bound to orders_topic with routing key: kitchen.{order_type}.{priority} -│ └── Read by: General kitchen workers -├── kitchen_dine_in_queue (durable: true, x-max-priority: 10) -│ └── Bound to orders_topic with routing key: kitchen.dine_in.* -│ └── Read by: dine_in specialized workers -├── kitchen_takeout_queue (durable: true, x-max-priority: 10) -│ └── Bound to orders_topic with routing key: kitchen.takeout.* -│ └── Read by: Takeout specialized workers -├── kitchen_delivery_queue (durable: true, x-max-priority: 10) -│ └── Bound to orders_topic with routing key: kitchen.delivery.* -│ └── Read by: Delivery specialized workers -└── notifications_queue (durable: true, auto-delete: false) - └── Bound to notifications_fanout - └── Read by: Notification subscribers, customer apps -``` - -### Message Formats -#### Order Message -**Purpose**: Contains complete order information for kitchen processing -**Sent to**: Kitchen queues (kitchen_queue, kitchen_dine_in_queue, etc.) -**Read by**: Kitchen Workers -**Routing**: Through orders_topic exchange using routing keys like "kitchen.delivery.10" +#### Request Body (`application/json`) ```json { - "order_number": "ORD_20241216_001", "customer_name": "John Doe", - "customer_type": "vip", - "order_type": "delivery", - "table_number": null, - "delivery_address": "123 Main St, City", + "order_type": "takeout", "items": [ - { - "name": "Margherita Pizza", - "quantity": 1, - "price": 15.99 - } - ], - "total_amount": 15.99, - "priority": 10 + { "name": "Margherita Pizza", "quantity": 1, "price": 15.99 }, + { "name": "Caesar Salad", "quantity": 1, "price": 8.99 } + ] } ``` -#### Status Update Message -**Purpose**: Notifies all interested parties about order status changes -**Sent to**: `notifications_fanout` exchange -**Read by**: Notification Subscribers -**Routing**: Broadcast to all queues bound to `notifications_fanout` exchange +#### Response Body (`application/json`) ```json { "order_number": "ORD_20241216_001", - "old_status": "received", - "new_status": "cooking", - "changed_by": "chef_mario", - "timestamp": "2024-12-16T10:32:00Z", - "estimated_completion": "2024-12-16T10:42:00Z" + "status": "received", + "total_amount": 24.98 } ``` -## Logging Format +### Logic -This structured logging format must be used by all services. Consistent logging is vital for debugging, monitoring, and auditing the system. +1. **Receive Request:** The service listens for an HTTP `POST` request on the `/orders` endpoint. -### JSON Log Format +2. **Input Validation:** The incoming JSON payload is validated against the following rules: -All services must implement structured logging in JSON format. +| `tag` | `required type` | `description` | +| --------------- | --------------- | -------------------------------------------------------------------------------------------------- | +| `customer_name` | strin | 1-100 characters. Must not contain special characters other than spaces, hyphens, and apostrophes. | +| `order_type` | strin | Must be one of: `'dine_in'`, `'takeout'`, or `'delivery'`. | +| `items` | arra | Must contain between 1 and 20 items. | +| `item.name` | strin | 1-50 characters. | +| `item.quantity` | intege | must be between 1 and 10. | +| `item.price` | decima | must be between `0.01` and `999.99`. | -**Mandatory Core Fields:** -The following keys must always be present in every log entry: -- `timestamp`: The time the log entry was created. -- `level`: The log level (e.g., INFO, ERROR). -- `service`: The name of the service emitting the log (e.g., `order-service`, `kitchen-worker`). -- `action`: A concise, machine-readable string describing the event (e.g., `order_received`, `db_error`). -- `message`: A human-readable description of the event. -- `hostname`: The hostname or unique identifier of the module emitting the log. -- `request_id`: A unique identifier for correlating requests/operations across multiple services. +- **Conditional Validation:** -**Format Example:** -```json -{ - "timestamp": "2024-12-16T10:30:15.123Z", - "level": "INFO", - "service": "order-service", - "hostname": "order-service-789abc", - "request_id": "a1b2c3d4e5f6", - "action": "order_received", - "message": "Order received and queued for processing", - "worker_name": "chef_mario", - "order_number": "ORD_20241216_001", - "duration_ms": 45, - "details": { - "customer_name": "John Doe", - "order_type": "delivery", - "priority": 10, - "total_amount": 15.99 - } -} +| `order_type` | `required fields` | `description` | `must not be present` | +| ------------ | ---------------------------------------------- | --------------------------------------------- | --------------------- | +| `'dine_in'` | `table_number` (integer, 1-100) | Table number at which the customer is served. | `delivery_address` | +| `'delivery'` | `delivery_address` (string, min 10 characters) | Address for delivery of the order by courier. | `table_number` | + +3. **Data Processing:** + + - **Calculate `total_amount`:** Sum the `price` \* `quantity` for all items in the order. + - **Assign `priority`:** Determine the order's priority based on its total amount. The highest applicable priority is used. + + | Priority | Criteria | + | -------- | ------------------------------------------- | + | `10` | Order total amount is greater than $100. | + | `5` | Order total amount is between $50 and $100. | + | `1` | All other standard orders. | + + - **Generate `order_number`:** Create a unique order number using the format `ORD_YYYYMMDD_NNN`. The `NNN` sequence should reset to `001` daily (based on UTC). This must be managed transactionally to prevent race conditions. + +4. **Database Transaction:** All database operations must be executed within a single transaction to ensure data integrity. + + - Insert the main order details into the `orders` table. + - Insert each item from the `items` array into the `order_items` table, linked by the new `order_id`. + - Log the initial status by inserting a record into the `order_status_log` table with `status: 'received'`. + +5. **Message Publishing:** + + - After the transaction is successfully committed, publish an `Order Message` to the `orders_topic` exchange in RabbitMQ. + - **Routing Key:** `kitchen.{order_type}.{priority}` (e.g., `kitchen.takeout.1`). + - **Message Properties:** The message must be persistent (`delivery_mode: 2`) and have its `priority` property set according to the assigned priority level. + - **Message Format:** + + ```json + { + "order_number": "ORD_20241216_001", + "customer_name": "John Doe", + "order_type": "delivery", + "table_number": null, + "delivery_address": "123 Main St, City", + "items": [{ "name": "Margherita Pizza", "quantity": 7, "price": 15.99 }], + "total_amount": 111.93, + "priority": 10 + } + ``` + +6. **Error Handling:** If validation fails, return an HTTP `400 Bad Request` response with a clear error message. If a database or RabbitMQ error occurs, return an HTTP `500 Internal Server Error`. + +7. **Send Response:** Return an HTTP `200 OK` response with the generated `order_number`, `status`, and `total_amount`. + +### Logs + +All log entries must be structured JSON written to `stdout`. Refer to the global [Logging Format](#logging-format) section for core fields (`timestamp`, `level`, `service`, etc.). + +| Level | Action | Description | +| ----- | ----------------------- | ----------------------------------------------------------------------------------- | +| INFO | service_started | On successful startup. | +| INFO | db_connected | On successful database connection. | +| INFO | rabbitmq_connected | On successful RabbitMQ connection. | +| DEBUG | order_received | When a new valid order is received. | +| DEBUG | order_published | When an order message is successfully published to RabbitMQ. | +| ERROR | validation_failed | When incoming order data fails validation. Include details of the validation error. | +| ERROR | db_transaction_failed | If the database transaction for creating an order fails. | +| ERROR | rabbitmq_publish_failed | If publishing the order message fails. | + +### Flags + +| Option | Type | Default | Description | +| ------------------ | ------ | ------- | ----------------------------------------------- | +| `--mode` | string | — | **Required.** Must be set to `order-service`. | +| `--port` | int | `3000` | The HTTP port for the API. | +| `--max-concurrent` | int | `50` | Maximum number of concurrent orders to process. | + +### Usage + +#### Launch the service + +```sh +./restaurant-system --mode=order-service --port=3000 ``` -**Error Object Shape:** -For `ERROR` level logs, an `error` object must be included with the following structure: +_Expected Log Output:_ + ```json -{ - "timestamp": "2024-12-16T10:35:00.000Z", - "level": "ERROR", - "service": "kitchen-worker", - "hostname": "kitchen-worker-xyz789", - "request_id": "a1b2c3d4e5f6", - "action": "db_query_failed", - "message": "Failed to retrieve order from database", - "error": { - "type": "sql.ErrNoRows", - "msg": "query failed: no rows in result set", - "stack": "internal/db/order.go:120" - } -} +{"timestamp":"2024-12-16T10:30:00.000Z","level":"INFO","service":"order-service","hostname":"order-service-789abc","request_id":"startup-001","action":"service_started","message":"Order Service started on port 3000","details":{"port":3000,"max_concurrent":50}} +{"timestamp":"2024-12-16T10:30:01.000Z","level":"INFO","service":"order-service","hostname":"order-service-789abc","request_id":"startup-001","action":"db_connected","message":"Connected to PostgreSQL database","duration_ms":250} +{"timestamp":"2024-12-16T10:30:02.000Z","level":"INFO","service":"order-service","hostname":"order-service-789abc","request_id":"startup-001","action":"rabbitmq_connected","message":"Connected to RabbitMQ exchange 'orders_topic'","duration_ms":150} ``` -### Log Levels Usage +#### Place an order using `curl` -- **ERROR:** Database connection failures, RabbitMQ connection drops, order validation failures, message publish/consume errors, system crashes. -- **WARN:** Message retry attempts, database query timeout warnings, configuration issues, order processing delays, worker disconnection/reconnection events. -- **INFO:** Order lifecycle events (received, cooking started, completed), worker status changes, service startup/shutdown, normal business operations, performance milestones. -- **DEBUG:** Detailed message content, database query execution details, internal processing steps, performance metrics, development and troubleshooting information. +```sh +curl -X POST http://localhost:3000/orders \ + -H "Content-Type: application/json" \ + -d '{ + "customer_name": "Jane Doe", + "order_type": "takeout", + "items": [ + {"name": "Margherita Pizza", "quantity": 1, "price": 15.99}, + {"name": "Caesar Salad", "quantity": 1, "price": 8.99} + ] + }' +``` -### Log Location and Format Rules: -- All logs must be emitted as single-line JSON (no pretty printing) to `stdout`. This is crucial for containerized environments where log collectors typically consume `stdout`. -- Logs must not contain Personally Identifiable Information (PII), such as `customer_address`, payment information, or other sensitive data. -- All log messages must be UTF-8 encoded, and newlines within log fields must be properly escaped. +--- -### Required Log Events: +## Feature: Kitchen Worker -- **Order received/processed/completed** (INFO level) -- **Worker connected/disconnected** (INFO level) -- **Status changes** (INFO level) -- **Error conditions and recoveries** (ERROR/WARN level) -- **Performance metrics** (INFO/DEBUG level) +### Context -## Configuration Management +The Kitchen Worker is a background service that simulates the kitchen staff. It consumes order messages from a queue, processes them, and updates their status in the database. It is the core processing engine of the restaurant. Multiple worker instances can run concurrently to handle high order volumes and can be specialized to process specific types of orders. -A clear configuration strategy is essential for deploying and running the services in different environments. +### Migrations -### Configuration Files +This service interacts with the following database tables. It is responsible for creating worker records and updating order statuses. -The system should be able use configuration files for database and RabbitMQ connections. +- **`workers` Table**: A registry for all kitchen workers, their specializations, and their current status. -```yaml -# Database Configuration -database: - host: localhost - port: 5432 - user: restaurant_user - password: restaurant_pass - database: restaurant_db + ```sql + create table workers ( + "id" serial primary key, + "created_at" timestamptz not null default now(), + "name" text unique not null, + "type" text not null, + "status" text default 'online', + "last_seen" timestamptz default current_timestamp, + "orders_processed" integer default 0 + ); + ``` -# RabbitMQ Configuration -rabbitmq: - host: localhost - port: 5672 - user: guest - password: guest - vhost: / - exchanges: - orders_topic: - type: topic - durable: true - notifications_fanout: - type: fanout - durable: true - queues: - kitchen_queue: - durable: true - x_max_priority: 10 - kitchen_dine_in_queue: - durable: true - x_max_priority: 10 - kitchen_takeout_queue: - durable: true - x_max_priority: 10 - kitchen_delivery_queue: - durable: true - x_max_priority: 10 - notifications_queue: - durable: true - auto_delete: false -``` +- **`orders` Table**: References this table to update order `status`, `completed_at`, and `processed_by` fields. (See [Order Service Migrations](#migrations) for table definition). +- **`order_status_log` Table**: References this table to insert status change events (`cooking`, `ready`). (See [Order Service Migrations](#migrations) for table definition). -## Resources +### API -- [RabbitMQ Documentation](https://www.rabbitmq.com/documentation.html) -- [RabbitMQ Docker Image](https://hub.docker.com/_/rabbitmq) -- [Go AMQP Client](https://github.com/rabbitmq/amqp091-go) -- [PostgreSQL Go Driver (pgx)](https://github.com/jackc/pgx/v5) +This service does not expose any API endpoints. It interacts with the system via RabbitMQ messages. -## General Criteria +### Logic -- Your code MUST be written in accordance with [gofumpt](https://github.com/mvdan/gofumpt). If not, you will automatically receive a score of `0`. -- Your program MUST compile successfully. -- Your program MUST NOT crash unexpectedly (any panics: `nil-pointer dereference`, `index out of range`, etc.). If this happens, you will receive `0` points during defense. -- Only built-in Go packages, `pgx/v5` and the official AMQP client (`github.com/rabbitmq/amqp091-go`) are allowed. If other packages are used, you will receive a score of `0`. -- RabbitMQ server MUST be running and available for connection. -- PostgreSQL database MUST be running and accessible for all services -- All RabbitMQ connections must handle reconnection scenarios -- Implement proper graceful shutdown for all services -- All database operations must be transactional where appropriate -- The project MUST compile with the following command in the project root directory: +1. **Startup and Registration:** -```sh -$ go build -o restaurant-system . -``` +- On startup, the worker must register itself by inserting (or updating) a record in the `workers` table with its unique name and type, marking itself **online**. +- **Duplicate check:** + + - If a record with the same `worker_name` already exists **and is marked online**, the worker must log `ERROR: worker_registration_failed` and terminate with exit code `1`. + - If a record with the same name exists **but is marked offline**, the new worker may safely start: it should update the existing record to reflect its set the status to **online**. -## Mandatory Part +2. **Message Consumption:** -### Baseline +- The worker connects to RabbitMQ and consumes messages from one or more kitchen queues (`kitchen_queue`, `kitchen_dine_in_queue`, etc.) which are bound to the `orders_topic` exchange. +- It must configure a prefetch count (`basic.qos`) to limit the number of unacknowledged messages it holds at once. -By default, your program should implement a basic order processing system using RabbitMQ work queue pattern to distribute orders among cooks. + > In RabbitMQ, `basic.qos` is a method used to configure the quality of service for consumers, specifically by controlling how many messages a consumer can receive without acknowledging them. + > + > _Using `basic.qos` is critical to prevent worker overload and distribute the load evenly between workers._ -#### Database Setup: +3. **Worker Specialization:** -Create all required tables as specified in the Database Schema section. Each service must connect to PostgreSQL and handle connection errors gracefully. +- If the `--order-types` flag is specified, the worker checks the `order_type` of the incoming message. +- If the worker is not specialized for that order type, it must negatively acknowledge the message (`basic.nack`) with `requeue=true`, returning it to the queue for another worker. -#### RabbitMQ Setup: + > _Using `basic.nack` with `requeue=true` is a way to return a message to the queue if the worker is not intended to handle it._ - - Queue name: `kitchen_queue` - - Exchange: topic exchange named `orders_topic` - - Routing key: `kitchen.{order_type}.{priority}` (e.g., `kitchen.delivery.10`). `order_type` can be `dine_in`, `takeout`, `delivery`. `priority` can be `10`, `5`, `1`. - - Queue should be durable and survive server restarts - - Messages should be persistent - - Connection must handle reconnection automatically +4. **Order Processing:** -### Order Service +- **Set Status to `cooking`:** Update the order's status to `'cooking'` and set `processed_by` to the worker's name in the `orders` table. +- **Log Status Change:** Insert a new record in `order_status_log`. +- **Transactional integrity**: Changes to the database (such as updating status or logging) must be transactional. If any step (e.g., inserting into order_status_log) fails, the previous status update in orders must be rolled back. +- **Idempotency**: If a message is reprocessed (due to a `nack` or failure), the worker must be able to detect that the order is already in the cooking status and avoid setting it again, in order to prevent redundant log entries or notifications. +- **Publish Status Update:** Publish a `Status Update Message` to the `notifications_fanout` exchange. -#### API Endpoint + - **Message Format:** -- **Endpoint:** `POST /orders` -- **Content-Type:** `application/json` + ```json + { + "order_number": "ORD_20241216_001", + "old_status": "received", + "new_status": "cooking", + "changed_by": "chef_mario", + "timestamp": "2024-12-16T10:32:00Z", + "estimated_completion": "2024-12-16T10:42:00Z" + } + ``` -#### Incoming Request Format +- **Simulate Cooking:** Pause execution to simulate cooking time based on the `order_type`: + - `dine_in`: 8 seconds. + - `takeout`: 10 seconds. + - `delivery`: 12 seconds. +- **Set Status to `ready`:** Update the order's status to `'ready'` and set the `completed_at` timestamp in the `orders` table. Increment the worker's `orders_processed` count in the `workers` table. +- **Log and Publish Again:** Log the status change to `order_status_log` and publish another `Status Update Message` to the `notifications_fanout` exchange. -```json -{ - "customer_name": "John Doe", - "order_type": "takeout", - "items": [ - {"name": "Margherita Pizza", "quantity": 1, "price": 15.99}, - {"name": "Caesar Salad", "quantity": 1, "price": 8.99} - ] -} -``` +5. **Message Acknowledgement:** Once all processing steps are complete, send a positive acknowledgement (`basic.ack`) to RabbitMQ to permanently remove the message from the queue. -#### Processing Logic + > _In RabbitMQ, `basic.ack` is a method used for positive acknowledgments. When a consumer receives a message and processes it successfully, it should send a basic.ack to the broker (RabbitMQ) to indicate that the message has been handled and can be removed from the queue. This ensures that messages are not lost if a consumer crashes or disconnects, as they will be redelivered to another consumer._ -1. **Receive HTTP POST request.** -2. **Validate data** against rules. -3. **Calculate `total_amount`**. -4. **Assign `priority`** based on rules (see [Priority Assignment Rules](#priority-assignment-rules) below). -5. **Generate `order_number`** (`ORD_YYYYMMDD_NNN`). This should be a daily sequence starting at 001, based on the UTC date. The sequence number should be managed either by a database counter or a transaction-safe query to determine the next number for the current UTC day. -6. **Store order** in PostgreSQL `orders` and `order_items` tables within a transaction. -7. **Log initial status** to `order_status_log`. -8. **Publish `Order Message` to RabbitMQ**: - - **Exchange:** `orders_topic`. - - **Routing Key:** `kitchen.{order_type}.{priority}` (e.g., `kitchen.takeout.1`). - - **Message Properties:** Set `delivery_mode: 2` (persistent). For priority queueing, map the priority level (`10`/`5`/`1`) to a numeric value and set it in the `priority` message property. -9. **Return HTTP JSON response.** +6. **Heartbeat Mechanism:** -#### Outgoing Response Format +- The worker sends a periodic heartbeat by updating its `last_seen` timestamp and `status` to `'online'` in the `workers` table. -```json -{ - "order_number": "ORD_20241216_001", - "status": "received", - "total_amount": 24.98 -} -``` +7. **Error and Redelivery Handling:** -#### Priority Assignment Rules +- If any processing step fails (e.g., database unavailable), the worker must negatively acknowledge the message (`basic.nack`) with `requeue=true` so it can be re-processed later. +- If there are data validation errors in the message that will never be corrected, the message should be sent to a Dead-Letter Queue (`DLQ`) to allow for manual analysis and to prevent the queue from being blocked. -The system assigns a priority level (`10`, `5`, `1`) to each order. If an order matches multiple criteria, the highest applicable priority is assigned. This priority level is used in the routing key. + > Dead Letter Queue (DLQ) is a specialized queue that stores messages that cannot be delivered or processed by their intended queue. It acts as a safety net, preventing failed messages from being lost and allowing for inspection, troubleshooting, and potential reprocessing. -| Priority | Criteria | -| :------- | :------------------------------------------ | -| `10` | Order total amount is greater than $100. | -| `5` | Order total amount is between $50 and $100. | -| `1` | All other standard orders. | +8. **Graceful Shutdown:** + - On receiving `SIGINT` or `SIGTERM`, the worker must: + 1. Stop consuming new messages from the queue. + 2. Finish processing its current in-flight order. + 3. Update its status to `'offline'` in the `workers` table. + 4. `nack` any other unacknowledged messages to requeue them. + 5. Exit gracefully. -#### Validation Rules & Edge Cases +### Logs -- `customer_name`: required, 1-100 characters, no special characters except spaces, hyphens, apostrophes. -- `order_type`: required, must be one of: 'dine_in', 'takeout', 'delivery'. -- `items`: required array, minimum 1 item, maximum 20 items per order. -- `item.name`: required, 1-50 characters. -- `item.quantity`: required, integer, 1-10 per item. -- `item.price`: required, decimal, 0.01-999.99. -- `table_number`: required for `dine_in` orders, 1-100. -- `delivery_address`: required for `delivery` orders, minimum 10 characters. -- **Conflicting Fields**: - - `dine_in` orders must NOT include `delivery_address`. - - `delivery` orders must NOT include `table_number`. -- **Duplicate Items**: Reject order if `items` contains duplicate entries by `name` that would violate the `quantity` rule (e.g., two "Margherita Pizza" entries summing to more than 10 quantity). -- **Database Transactions**: All database operations for order creation must be transactional. +| Level | Action | Description | +| ----- | -------------------------- | --------------------------------------------------- | +| INFO | worker_registered | On successful registration in the `workers` table. | +| DEBUG | order_processing_started | When an order is picked from the queue. | +| DEBUG | order_completed | When an order is fully processed. | +| DEBUG | heartbeat_sent | When a heartbeat is successfully sent. | +| INFO | graceful_shutdown | When the worker starts its shutdown sequence. | +| ERROR | worker_registration_failed | If the worker name is a duplicate. | +| ERROR | message_processing_failed | For unrecoverable processing errors. | +| ERROR | db_connection_failed | When the database is unreachable after all retries. | -#### Log Requirements +### Flags -Use structured logging format as defined in the [Logging Format](#logging-format) section. +| Option | Type | Default | Description | +| ---------------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------- | +| `--mode` | string | — | **Required.** Must be set to `kitchen-worker`. | +| `--worker-name` | string | — | **Required.** Unique name for the worker (e.g., `chef_mario`). | +| `--order-types` | string | — | Optional. Comma-separated list of order types the worker can handle (e.g., `dine_in,takeout`). If omitted, handles all. | +| `--heartbeat-interval` | int | `30` | Interval (seconds) between heartbeats. | +| `--prefetch` | int | `1` | RabbitMQ prefetch count, limiting how many messages the worker receives at once. | -#### Flags +### Usage -- `--mode`: Service mode (required: `order-service`) -- `--port`: HTTP port for REST API (default: 3000) -- `--max-concurrent`: Maximum concurrent orders (default: 50). +1. **Launch a general worker:** -#### Worked Example + ```sh + ./restaurant-system --mode=kitchen-worker --worker-name="chef_anna" --prefetch=1 + ``` -```sh -# Start the Order Service -./restaurant-system --mode=order-service --port=3000 --max-concurrent=50 -# Expected console output: -{"timestamp":"2024-12-16T10:30:00.000Z","level":"INFO","service":"order-service","hostname":"order-service-789abc","request_id":"startup-001","action":"service_started","message":"Order Service started on port 3000","details":{"port":3000,"max_concurrent":50}} -{"timestamp":"2024-12-16T10:30:01.000Z","level":"INFO","service":"order-service","hostname":"order-service-789abc","request_id":"startup-001","action":"db_connected","message":"Connected to PostgreSQL database","duration_ms":250} -{"timestamp":"2024-12-16T10:30:02.000Z","level":"INFO","service":"order-service","hostname":"order-service-789abc","request_id":"startup-001","action":"rabbitmq_connected","message":"Connected to RabbitMQ exchange 'orders_topic'","duration_ms":150} -``` +2. **Launch specialized workers:** -```sh -# Place order -curl -X POST http://localhost:3000/orders \ - -H "Content-Type: application/json" \ - -d '{ - "customer_name": "John Doe", - "order_type": "takeout", - "items": [ - {"name": "Margherita Pizza", "quantity": 1, "price": 15.99}, - {"name": "Caesar Salad", "quantity": 1, "price": 8.99} - ] - }' + ```sh + # This worker only handles dine-in orders + ./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --order-types="dine_in" & -# Expected HTTP Response: -# {"order_number": "ORD_20241216_001", "status": "received", "total_amount": 24.98} -``` + # This worker only handles delivery orders + ./restaurant-system --mode=kitchen-worker --worker-name="chef_luigi" --order-types="delivery" & + ``` -### Order Types +--- -#### Order Type Processing +## Feature: Tracking Service -- **dine_in:** - - Requires `table_number`. - - Shorter cooking time (8 seconds). -- **takeout:** - - Standard processing. - - Medium cooking time (10 seconds). -- **delivery:** - - Requires `delivery_address`. - - Longer cooking time (12 seconds). +### Context -#### Worker Specialization +The Tracking Service provides visibility into the restaurant's operations. It offers a read-only HTTP API for external clients (like a customer-facing app or an internal dashboard) to query the current status of orders, view an order's history, and monitor the status of all kitchen workers. It directly queries the database and does not interact with RabbitMQ. -- Workers can be configured to only accept certain types of orders using the `--order-types` flag. If a worker receives an order it cannot process, it must negatively acknowledge the message (`basic.nack`) and requeue it so another worker can pick it up. +### Migrations -### Kitchen Worker +This service reads data from the following tables but does not write to them. -#### Incoming Message Format +- **`orders` Table**: Referenced to get the current status of an order. (See [Order Service Migrations](#migrations) for definition). +- **`order_status_log` Table**: Referenced to retrieve the full history of an order's status changes. (See [Order Service Migrations](#migrations) for definition). +- **`workers` Table**: Referenced to get the status of all registered kitchen workers. (See [Kitchen Worker Migrations](#migrations-1) for definition). -- **Queue:** `kitchen_queue` (or specialized queues like `kitchen_delivery_queue`) -- **Format:** See [Order Message](#order-message) section +### API -#### Processing Logic +#### Retrieves the current status and details of a single order. -1. **Consume Message:** Consume an order message from a bound kitchen queue. -2. **Process Order:** - - Update the order's `status` to `cooking` in the `orders` table. - - Log the status change in the `order_status_log` table. - - Publish a `status_update` message to the `notifications_fanout` exchange. -3. **Simulate Cooking:** Simulate the cooking process with a configurable duration. -4. **Update Status to 'ready':** - - Update the order's `status` to `ready` in the `orders` table. - - Update the `completed_at` timestamp. - - Increment the `orders_processed` counter for the worker in the `workers` table. - - Log the status change in the `order_status_log` table. - - Publish a `status_update` message to the `notifications_fanout` exchange. -5. **Acknowledge Receipt:** Acknowledge the message to RabbitMQ (`basic.ack`). -6. **Handle Processing Failure:** If any step in processing fails (e.g., database error, message publish error), reject the message with `basic.nack(requeue=true)` to return it to the queue for redelivery. +```http +GET /orders/{order_number}/status +``` -#### Log Requirements +#### Response Body (`application/json`) -Use structured logging format as defined in the [Logging Format](#logging-format) section. +```json +{ + "order_number": "ORD_20241216_001", + "current_status": "cooking", + "updated_at": "2024-12-16T10:32:00Z", + "estimated_completion": "2024-12-16T10:42:00Z", + "processed_by": "chef_mario" +} +``` -#### Flags +#### Retrieves the complete lifecycle history of an order. -- `--worker-name`: Unique name for the worker (required). -- `--order-types`: Comma-separated list of order types the worker can process (e.g., `dine_in,takeout`). +```http +GET /orders/{order_number}/history +``` -#### Worked Example +#### Response Body (`application/json`) -```sh -# Start multiple workers with different specializations -$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_mario" --order-types="dine_in" & -$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_luigi" --order-types="delivery" & -$ ./restaurant-system --mode=kitchen-worker --worker-name="chef_anna" & +```json +[ + { + "status": "received", + "timestamp": "2024-12-16T10:30:00Z", + "changed_by": "order-service" + }, + { + "status": "cooking", + "timestamp": "2024-12-16T10:32:00Z", + "changed_by": "chef_mario" + } +] ``` -### Multiple Workers & Load Balancing +#### Retrieves the status of all registered kitchen workers. -#### Worker Management +```http +GET /workers/status +``` + +#### Response Body (`application/json`) -- **Registration:** When a `kitchen-worker` starts, it should register itself in the `workers` table in the database. -- **Status Tracking:** The worker's status (`online`, `offline`, `processing`) and `last_seen` should be updated regularly. - - `online` indicates the worker is running and ready to receive orders. - - `processing` should be set when the worker is actively handling an order. - - `offline` should be set during graceful shutdown. +```json +[ + { + "worker_name": "chef_mario", + "status": "online", + "orders_processed": 5, + "last_seen": "2024-12-16T10:35:00Z" + }, + { + "worker_name": "chef_luigi", + "status": "offline", + "orders_processed": 3, + "last_seen": "2024-12-16T10:30:01Z" + } +] +``` -#### Heartbeat & Offline Detection +### Logic -- Each `kitchen-worker` sends a heartbeat every `--heartbeat-interval` (default 30 seconds) by executing an `UPDATE` query on the `workers` table: `UPDATE workers SET last_seen = now(), status='online' WHERE name = `. This indicates the worker is alive and active. -- The `tracking-service` (or a dedicated monitoring component) should periodically check the `workers` table. A worker is considered `offline` if `now() - last_seen` is greater than `2 * --heartbeat-interval`. +1. **Receive Request:** The service listens for HTTP `GET` requests on its defined endpoints. -#### Load Balancing +2. **Query Database:** -- **Round-Robin:** By default, RabbitMQ will distribute orders to available workers in a round-robin fashion. -- **Priority Queues:** If queues are configured with `x-max-priority`, RabbitMQ delivers higher-priority messages before lower-priority regardless of round-robin. -- **Worker Specialization:** Workers can be configured to only accept certain types of orders using the `--order-types` flag. +- For `/orders/{order_number}/status`, it queries the `orders` table for the specified `order_number`. +- For `/orders/{order_number}/history`, it queries the `order_status_log` table, joining with `orders` to find the correct order. +- For `/workers/status`, it queries the `workers` table. It should also include logic to determine if a worker is `offline` by checking if `now() - last_seen` exceeds a certain threshold (e.g., `2 * heartbeat-interval`). -#### Redelivery Algorithm +3. **Format Response:** It formats the query results into the specified JSON structure. -- **Prefetch Count:** Each `kitchen-worker` should configure its RabbitMQ consumer with `basic.qos(prefetch_count=N)` (where N is the `--prefetch` flag, default 1). This limits the number of unacknowledged messages a worker can receive at a time. -- **Clean Disconnection:** If a worker disconnects cleanly (e.g., graceful shutdown), it should `basic.nack(requeue=true)` any outstanding unacknowledged deliveries. This ensures messages are returned to the queue for other workers. -- **Unclean Disconnection/Crash:** If a worker crashes or disconnects uncleanly, RabbitMQ will automatically re-queue any unacknowledged messages after a timeout, making them available to other workers. +4. **Error Handling:** If an `order_number` is not found, it should return an HTTP `404 Not Found`. For database errors, it should return an HTTP `500 Internal Server Error`. -#### Log Requirements +### Logs -Use structured logging format as defined in the [Logging Format](#logging-format) section. +| Level | Action | Description | +| ----- | ---------------- | -------------------------------------------------------------- | +| INFO | service_started | On successful startup. | +| DEBUG | request_received | On receiving any API request. Include endpoint and parameters. | +| ERROR | db_query_failed | If any database query fails. | -#### Flags +### Flags -- `--worker-name`: Unique worker name (required). -- `--order-types`: Comma-separated order types to process. -- `--heartbeat-interval`: Interval in seconds for sending worker heartbeats (default: 30). -- `--prefetch`: RabbitMQ prefetch count for the worker (default: 1). +| Option | Type | Default | Description | +| -------- | ------ | ------- | ------------------------------------------------ | +| `--mode` | string | — | **Required.** Must be set to `tracking-service`. | +| `--port` | int | `3002` | The HTTP port for the API. | -#### Validation Rules & Edge Cases +### Usage -- **Duplicate Worker Name**: If the `INSERT` into `workers` violates name uniqueness, log `ERROR` and exit with status code `1`. -- **Graceful Shutdown**: Upon receiving a shutdown signal (e.g., `SIGINT`, `SIGTERM`), a worker should immediately stop accepting new orders from the queue. It must then finish processing any in-flight order, set its status to `offline` in the `workers` table, attempt to `basic.nack(requeue=true)` any unacknowledged messages, and then gracefully exit. This ensures that no work is lost and the system state remains consistent. -- **Database Unreachable**: Handle scenarios where the database is temporarily unreachable during heartbeat updates or registration. Implement retry mechanisms (e.g., three attempts with exponential back-off starting at 1 second, capped at 30 seconds) for database write operations. +1. **Launch the service:** -### Tracking Service + ```sh + ./restaurant-system --mode=tracking-service --port=3002 + ``` -This service provides HTTP API endpoints for querying order status, history, and worker information. It reads from the database and does not produce or consume messages. +2. **Query for an order's status:** -#### API Endpoints + ```sh + curl http://localhost:3002/orders/ORD_20241216_001/status + ``` -- **`GET /orders/{order_number}/status`:** +3. **Query for worker status:** - - **Purpose:** To get the current status of a specific order. - - **Response Format:** - ```json - { - "order_number": "ORD_20241216_001", - "current_status": "cooking", - "updated_at": "2024-12-16T10:32:00Z", - "estimated_completion": "2024-12-16T10:42:00Z", - "processed_by": "chef_mario" - } - ``` + ```sh + curl http://localhost:3002/workers/status + ``` -- **`GET /orders/{order_number}/history`:** +--- - - **Purpose:** To get the full status history of an order. - - **Response Format:** - ```json - [ - {"status": "received", "timestamp": "2024-12-16T10:30:00Z", "changed_by": "order-service"}, - {"status": "cooking", "timestamp": "2024-12-16T10:32:00Z", "changed_by": "chef_mario"} - ] - ``` +## Feature: Notification Service -- **`GET /workers/status`:** +### Context - - **Purpose:** To get the status of all registered kitchen workers. - - **Response Format:** - ```json - [ - {"worker_name": "chef_mario", "status": "online", "orders_processed": 5, "last_seen": "2024-12-16T10:35:00Z"}, - {"worker_name": "chef_luigi", "status": "online", "orders_processed": 3, "last_seen": "2024-12-16T10:35:01Z"} - ] - ``` +The Notification Service is a simple subscriber that demonstrates the fanout capabilities of the messaging system. It listens for all order status updates published by the Kitchen Workers and displays them. In a real-world scenario, this service could be extended to send push notifications, emails, or SMS messages to customers. -#### Flags +### Migrations -- `--mode`: Service mode (required: `tracking-service`) -- `--port`: HTTP port for REST API (default: 3002) +This service does not interact with the database. -### Notification Service +### API -This service is responsible for consuming status update messages and notifying relevant parties. It demonstrates the publish/subscribe pattern, where multiple subscribers can listen for events without the publisher (Kitchen Worker) knowing about them. +This service does not expose any API endpoints. It outputs information to the console. -#### Incoming Message Format +### Logic -- **Queue:** `notifications_queue` (bound to `notifications_fanout` exchange) -- **Format:** See [Status Update Message](#status-update-message) section +1. **Startup and Connection:** + - Connects to RabbitMQ. + - Ensures the `notifications_fanout` exchange and the `notifications_queue` exist and that the queue is bound to the exchange. +2. **Message Consumption:** + - Consumes `Status Update Messages` from the `notifications_queue`. The message format is defined in the [Kitchen Worker Logic](#logic-1) section. +3. **Display Notification:** + - Upon receiving a message, it parses the JSON and prints a human-readable notification to standard output. + - Example: `Notification for order ORD_20241216_001: Status changed from 'received' to 'cooking' by chef_mario.` +4. **Message Acknowledgement:** It sends a `basic.ack` to RabbitMQ to confirm the message has been processed and can be deleted from the queue. -#### Processing Logic +### Logs -1. **Consume Message:** Consume a status update message from the `notifications_queue`. -2. **Display Notification:** Print the received notification to standard output in a clear, human-readable format. -3. **Acknowledge Receipt:** Acknowledge the message to RabbitMQ (`basic.ack`) to remove it from the queue. +| Level | Action | Description | +| ----- | -------------------------- | --------------------------------------------------------------------------------------------------- | +| INFO | service_started | On successful startup. | +| DEBUG | notification_received | When a status update message is received. Include key details like `order_number` and `new_status`. | +| ERROR | rabbitmq_connection_failed | If the connection to RabbitMQ fails. | -#### Log Requirements +### Flags -Use structured logging format as defined in the [Logging Format](#logging-format) section for events like service startup and message consumption. +| Option | Type | Default | Description | +| -------- | ------ | ------- | ------------------------------------------------------- | +| `--mode` | string | — | **Required.** Must be set to `notification-subscriber`. | -#### Flags +### Usage -- `--mode`: Service mode (required: `notification-subscriber`) +1. **Launch one or more subscribers:** -#### Worked Example + ```sh + # Terminal 1 + ./restaurant-system --mode=notification-subscriber -```sh -# Start a subscriber to listen for all notifications -./restaurant-system --mode=notification-subscriber + # Terminal 2 + ./restaurant-system --mode=notification-subscriber + ``` -# Start another subscriber interested only in notifications for "John Doe" -./restaurant-system --mode=notification-subscriber +2. **Observe the output:** As Kitchen Workers process orders, both running subscribers will print the same notification messages to their respective consoles, demonstrating the broadcast pattern. + _Expected Log Output:_ + ```json + { + "timestamp": "2024-12-16T10:32:05.000Z", + "level": "INFO", + "service": "notification-subscriber", + "hostname": "notification-sub-1", + "request_id": "a1b2c3d4e5f6", + "action": "notification_received", + "message": "Received status update for order ORD_20241216_001", + "details": { "order_number": "ORD_20241216_001", "new_status": "cooking" } + } + ``` + _Expected Console Output:_ + `Notification for order ORD_20241216_001: Status changed from 'received' to 'cooking' by chef_mario.` -# Expected console output for the general subscriber when an order status changes: -# Notification for order ORD_20241216_001: Status changed from 'received' to 'cooking' by chef_mario. -{"timestamp":"2024-12-16T10:32:05.000Z","level":"INFO","service":"notification-subscriber","hostname":"notification-sub-1","request_id":"a1b2c3d4e5f6","action":"notification_received","message":"Received status update for order ORD_20241216_001","details":{"order_number":"ORD_20241216_001","new_status":"cooking"}} -``` +--- ## Support @@ -733,4 +763,4 @@ Yelnar Moldabekov Contacts: - Email: [mranle91@gmail.com](mailto:mranle91@gmail.com) -- [GitHub](https://github.com/ymoldabe/) \ No newline at end of file +- [GitHub](https://github.com/ymoldabe/)