Refactor MQTT course and bound backend message processing
Move lesson source into the teaching structure, align the course path with the learner's Go and DevOps experience, and add the interactive lesson guides. Add advanced DevOps lifecycle study as a separate section. Bound MQTT message work and drain accepted jobs during shutdown; align the backend implementation with the revised course through Section 6.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# 🥤 Lesson 12 — Implement representative vending workflows
|
||||
|
||||
## 🎬 Start with the system story
|
||||
|
||||
**❓ Question:** **A vending machine reports a sale, receives a new catalog, and asks for configuration. Which side owns each fact, and which MQTT message proves that a change reached the device?**
|
||||
|
||||
**💬 Answer:** The device owns what it observed or applied locally; the backend owns the accepted business record and the version it intends the device to use. A broker PUBACK proves only a QoS delivery hop. The device must publish an application result or reported revision to show what it accepted or applied.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
D[Device simulator] -->|sales report / health / logs| B[Mosquitto]
|
||||
B -->|delivered publication| I[Backend ingestion]
|
||||
I -->|validate, deduplicate, record| S[In-memory state]
|
||||
A[Backend workflow] -->|catalog or config request| B
|
||||
B -->|command / desired state| D
|
||||
D -->|accepted / completed / reported revision| B
|
||||
```
|
||||
|
||||
This lesson extends the contracts and command/response flow with synthetic vending data. Keep all data disposable and simulated. Do not connect the exercises to production devices, payment processing, or a production database.
|
||||
|
||||
## 💰 1. Ingest and deduplicate sales
|
||||
|
||||
> **❓ Question:** **The simulator republishes report `sale-0042` after losing its response. How do you avoid counting the same sale twice?**
|
||||
|
||||
The backend parses the topic and validates the report as Lesson 5 established. Then it uses a stable application identity such as `(device_id, report_id)` to detect repeats. An MQTT packet identifier is scoped to a protocol exchange and is not a durable sale ID.
|
||||
|
||||
For this lesson, keep the deduplication map in memory and make its limitation explicit: a process restart forgets the map. For each new report, record the report ID, its accepted/rejected result, and a synthetic effect count. For a duplicate, return or log the prior outcome without repeating the effect. Keep malformed or mismatched reports out of business state.
|
||||
|
||||
The simulator may publish an application-level receipt keyed by report ID. That receipt can mean “the backend saved this report in its current state”; it must not imply durable storage until Lesson 13 adds PostgreSQL. MQTT PUBACK and the report receipt are separate signals.
|
||||
|
||||
### 🧪 Predict, then try it
|
||||
|
||||
1. Publish the same valid report twice with the same report ID. Predict the effect counter and the two MQTT deliveries.
|
||||
2. Repeat with a changed amount but the same ID. Define whether the backend rejects ID reuse with different content or returns the original result; do not silently apply a second meaning.
|
||||
3. Restart the backend and replay the report. Observe that the in-memory implementation cannot deduplicate across restart.
|
||||
|
||||
**Expected evidence:** Two broker deliveries, one business effect before restart, and a documented loss of deduplication knowledge after restart.
|
||||
|
||||
## 📦 2. Deliver a versioned product catalog
|
||||
|
||||
> **❓ Question:** **If a device receives catalog revision 8 after revision 9, should it replace its current catalog?**
|
||||
|
||||
Treat a catalog as versioned application data. Give it a schema version, a stable message ID, a catalog/job ID, and a monotonically increasing revision. The backend publishes the intended catalog; the simulator validates the topic and payload, compares revisions, and reports the revision it applied. Older revisions must not overwrite newer local state.
|
||||
|
||||
Use a nonretained command when each delivery represents a one-time operation. If the catalog is modeled as current desired state, Lesson 10's retained-state rules may be more appropriate: publish a retained snapshot keyed by device, and let the device report the revision it has applied. Decide which model you are testing; do not mix “deliver this job once” with “this is the latest state.”
|
||||
|
||||
```text
|
||||
backend publishes catalog revision 9
|
||||
broker PUBACK → confirms backend-to-broker QoS hop only
|
||||
device receives catalog → validates and compares revision
|
||||
device publishes applied revision 9 → application evidence
|
||||
```
|
||||
|
||||
An application response should include the job or catalog ID and revision, so the backend can match it to the intended update. Do not claim that a successful publish or PUBACK means the catalog was installed.
|
||||
|
||||
### 🧪 Predict, then try it
|
||||
|
||||
1. Publish revision 9, then revision 8. Predict the simulator's reported revision.
|
||||
2. Send revision 9 twice with the same job ID. Confirm the second delivery does not reapply the update.
|
||||
3. Restart the simulator and explain which revision state is lost in this in-memory stage.
|
||||
|
||||
## ⚙️ 3. Model configuration fetch and update
|
||||
|
||||
> **❓ Question:** **What should happen when a device asks for configuration while the backend is preparing a newer revision?**
|
||||
|
||||
Give each configuration snapshot a device identity, revision, and schema version. For device-initiated fetch, the device publishes a request with a request ID; the backend replies on an authorized response destination or the defined device configuration topic. For a backend-initiated update, the backend publishes the desired revision and the device reports the applied revision.
|
||||
|
||||
Define a conflict rule before coding. A simple lab rule is “accept only revisions newer than the simulator's current revision.” If requests can race with updates, include the request ID or expected revision in the response and discard stale responses. Correlation Data can associate an MQTT response with a request, but it is not authorization and does not replace the application revision.
|
||||
|
||||
```text
|
||||
device → config/request (request_id, known_revision)
|
||||
backend → validate request and select current revision
|
||||
backend → config/response (request_id, revision, settings)
|
||||
device → validate request_id and revision; apply only if current
|
||||
device → reported revision (application confirmation)
|
||||
```
|
||||
|
||||
Use synthetic settings only. Never put credentials or secrets in a retained configuration message; retained data may be replayed to later subscribers.
|
||||
|
||||
## ❤️ 4. Add health and bounded logs
|
||||
|
||||
> **❓ Question:** **Does a recent heartbeat prove that a machine is healthy and ready to vend?**
|
||||
|
||||
No. Keep three observations distinct: MQTT connection status, recent application heartbeat, and device-reported health. A connected client can be malfunctioning; a fresh heartbeat can report an error; a Will indicates an unexpected connection loss but does not diagnose the device.
|
||||
|
||||
For log events, bound payload size and event frequency, sanitize control characters and secrets, and avoid logging full untrusted payloads. Decide which events are disposable telemetry and which require durable retention. Section 13 introduces durable persistence for important reports.
|
||||
|
||||
## 🔎 5. Compare with the existing backend
|
||||
|
||||
Use the existing Robot Market backend as a read-only reference. Trace the source path for sales ingestion, catalog/config publication, and device health. Record the actual topic, contract identity, acknowledgement, retry, and deduplication behavior you observe. Do not infer that a field or identifier has the same meaning as the lab just because the names look similar.
|
||||
|
||||
### 🧪 Review questions
|
||||
|
||||
1. Why is an MQTT packet ID unsuitable as a sales report ID?
|
||||
2. What does a broker PUBACK prove in the catalog flow, and what proves device application?
|
||||
3. How does the simulator prevent an older revision from replacing a newer one?
|
||||
4. Which state disappears when the backend or simulator restarts at this stage?
|
||||
5. Why are connection status, heartbeat freshness, and device health different signals?
|
||||
|
||||
> [!answer]- 📋 Short answers
|
||||
>
|
||||
> 1. Packet IDs are protocol-scoped and reusable; business identity must remain stable across retries and restarts.
|
||||
> 2. PUBACK proves the publisher-to-broker QoS exchange; a validated device result or reported revision is application evidence.
|
||||
> 3. It stores the highest applied revision and rejects older revisions.
|
||||
> 4. In-memory deduplication, business effects, command state, and applied simulator revision.
|
||||
> 5. Each measures a different boundary: transport connection, recent application message, and the device's own reported condition.
|
||||
|
||||
**Done when:** A duplicate sales report causes one in-memory effect, stale catalog/config revisions are rejected, and your notes distinguish transport acknowledgement from device application evidence.
|
||||
|
||||
**Next:** [Lesson 13 — Persist reports and command state](13-persist-reports-command-state.md) replaces restart-losing business state with PostgreSQL transactions and durable MQTT admission.
|
||||
Reference in New Issue
Block a user