Files
Ryan fb30e8e884 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.
2026-10-22 10:19:56 +03:30

255 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🧭 Lesson 0 — The broker, Go backend, and device lifecycle
> [!toc]- 📑 Contents
>
> - [[#🌐 The whole system|🌐 The whole system]]
> - [[#🖥️ 1. Set up the broker|🖥️ Broker setup]]
> - [[#⚙️ 2. Configure the backend and device|⚙️ Client configuration]]
> - [[#🔌 3. Connect both clients|🔌 Connections]]
> - [[#📬 4. Register subscriptions|📬 Subscriptions]]
> - [[#📨 5. Publish and route messages|📨 Message flow]]
> - [[#🔄 6. Recover and stop|🔄 Recovery and shutdown]]
> - [[#🗺️ Where each part is covered|🗺️ Lesson coverage map]]
> - [[#🧪 Understanding check|🧪 Understanding check]]
> - [[#🔗 Related lessons|🔗 Related lessons]]
## 🌐 The whole system
Picture three independently running programs: a broker, a Go backend, and a device. The backend and device are both MQTT clients. The broker accepts their connections, checks credentials and permissions, stores subscriptions, and routes publications.
The backend may have more application responsibilities, but MQTT does not give it a special connection type. This course applies the MQTT fundamentals you already know to a Go application using MQTT 5.
```mermaid
flowchart LR
C[Broker configuration<br/>listener, accounts, ACL] --> B[Mosquitto broker]
G[Go backend configuration<br/>URL, client ID, credentials] --> GL[Go backend + Paho]
D[Device configuration<br/>URL, client ID, credentials] --> DEV[Device application + MQTT client]
GL <-->|MQTT connection and messages| B
DEV <-->|MQTT connection and messages| B
```
The broker has its own setup and runtime. Each client has its own configuration and runtime. The backend and device do not need each other's network address; each connects to the broker.
---
## 🖥️ 1. Set up the broker
**❓ Question:** **Before a client arrives, how does the broker know where to listen, which credentials to accept, and which topics each identity may use?**
**💬 Answer:** Broker configuration supplies the listener, authentication source, and topic permissions. The clients present credentials; they do not create their own broker accounts.
Those decisions belong to broker configuration or to an authentication/authorization service configured by the broker. In this lab, Mosquitto is configured with a listener, a password file, and an ACL file. The `backend` and `simulator` accounts are created before the clients connect.
```mermaid
flowchart TD
OP[Operator prepares lab files] --> CONF[Listener and authentication config]
OP --> USERS[Backend and simulator accounts]
OP --> ACL[Per-identity topic permissions]
CONF -->|broker startup| B[Mosquitto]
USERS --> B
ACL --> B
```
The broker authenticates a client when it connects. It checks authorization when the client asks to subscribe or publish. Successful authentication does not automatically grant access to every topic. The broker can load local files, or a deployment can configure an external authentication provider; clients do not create their own broker accounts as they arrive.
For this learning environment, the broker is isolated and uses synthetic lab data. Its setup is described in [Lesson 1](01-lab-setup.md) and [Broker operations](broker-operations.md).
📚 **Covered in:** [Lesson 1 — Lab setup](01-lab-setup.md) configures Mosquitto, accounts, and ACLs. [Broker operations](broker-operations.md) covers maintenance; [Lesson 11 — Security](11-security-boundaries.md) develops authentication and authorization boundaries.
---
## ⚙️ 2. Configure the backend and device
**❓ Question:** **If both programs are MQTT clients, what distinguishes the backend's connection from the device's connection?**
**💬 Answer:** Each process has its own Client ID, credentials, configuration, and subscriptions. In this lab, the username selects broker ACL rules; a Client ID identifies an MQTT connection/session and is not a password.
Each process has its own configuration and client identity. Both need a broker URL and credentials accepted by that broker. They also need distinct Client IDs while running at the same time. Their usernames determine which broker ACL rules apply in this lab; Client ID identifies the MQTT connection/session and is not a password.
```mermaid
flowchart LR
T[TOML defaults] --> M[Configuration loader]
E[Environment secrets and overrides] --> M
F[Explicit CLI overrides] --> M
M --> V[Validate effective settings]
V -->|backend identity| BC[Backend client config]
V -->|simulator identity| DC[Device client config]
```
The Go project uses TOML for ordinary settings and `TEST_MQTT5_...` environment variables for credentials and overrides. `config.Load(...)` merges and validates the settings before MQTT startup. The simulator is another program with its own configuration; it is not a child session inside the backend.
Connection settings are distinct from message choices. Keep Alive, session behavior, and a Will are connection options. Topic filters and requested QoS belong to subscriptions. Topic, payload, QoS, and Retain belong to publications.
📚 **Covered in:** [Lesson 2 — Connection](02-go-connection.md) introduces the client settings; [Lesson 3 — Typed configuration](03-typed-configuration.md) builds the loader, precedence, and validation.
---
## 🔌 3. Connect both clients
**❓ Question:** **Once the Go process has configuration, what must it prove before it can claim to be connected?**
**💬 Answer:** It needs an accepted CONNACK. Creating a connection manager is not enough, and a successful connection does not prove that subscriptions or business operations succeeded.
The program parses the broker URL, creates a Paho/AutoPaho configuration, and starts the connection manager. AutoPaho manages the connection lifecycle and reconnect attempts; Paho implements MQTT protocol exchanges. The manager being created is not yet proof of a successful connection. The broker must accept MQTT `CONNECT` with `CONNACK`, and the application can wait for that state with `AwaitConnection`.
```mermaid
sequenceDiagram
participant G as Go backend / Paho
participant B as Broker
participant D as Device / MQTT client
G->>B: TCP connection, then MQTT CONNECT
B->>B: Authenticate backend identity
B-->>G: CONNACK accepted or rejected
D->>B: TCP connection, then MQTT CONNECT
B->>B: Authenticate device identity
B-->>D: CONNACK accepted or rejected
```
The backend and device go through this independently. A successful `CONNACK` proves that a client connection was accepted. It does not prove a subscription was accepted, a device is online, or an application operation completed.
📚 **Covered in:** [Lesson 2 — Connection](02-go-connection.md) follows CONNECT, CONNACK, reconnect, and shutdown. [Lesson 4 — Publish and subscribe](04-publish-subscribe-simulator.md) connects both Go processes.
---
## 📬 4. Register subscriptions
**❓ Question:** **The broker accepted the connection. How does it learn which messages this client wants to receive?**
**💬 Answer:** The client sends SUBSCRIBE topic filters, and the broker reports each filter's result in SUBACK. The local Paho handler is a separate step that processes publications the broker delivers.
The client sends `SUBSCRIBE` requests containing topic filters. The broker checks each request against its rules and returns `SUBACK` results. In the Go application, a local callback is also configured so Paho knows what to do when a matching publication arrives. The broker-side subscription and the Go-side callback are two separate parts of receiving a message.
```mermaid
sequenceDiagram
participant G as Go backend
participant B as Broker
participant D as Device
G->>B: SUBSCRIBE device telemetry and responses
B-->>G: SUBACK per-filter result
D->>B: SUBSCRIBE device commands
B-->>D: SUBACK per-filter result
```
The backend commonly listens for telemetry and responses. The device listens for its commands. The client should inspect the subscription result and establish required subscriptions before publishing work that depends on a receiver already listening.
📚 **Covered in:** [Lesson 4 — Publish and subscribe](04-publish-subscribe-simulator.md) builds the subscriptions and checks readiness. [Lesson 9 — Sessions and recovery](09-sessions-recovery.md) explores when subscriptions survive a reconnect; [Lesson 16 — MQTT 5 controls](16-mqtt5-controls.md) examines subscription options.
---
## 📨 5. Publish and route messages
**❓ Question:** **Once subscriptions exist, how does a command travel from backend code to the device application?**
**💬 Answer:** The backend publishes bytes to a topic, the broker routes them to matching subscriptions, and the device's MQTT client passes the publication to application code. QoS acknowledgements cover a delivery hop; the device's application response carries business evidence.
The backend serializes an application value into bytes, publishes those bytes to a concrete topic, and the broker routes the publication to matching subscribed clients. Paho's receive callback runs inside the client process; the application can copy and enqueue the message for its main loop to handle.
```mermaid
sequenceDiagram
participant G as Go backend
participant B as Broker
participant D as Device
G->>B: PUBLISH device-001/commands, QoS 1
B-->>G: PUBACK for backend-to-broker hop
B->>D: PUBLISH matching command
D-->>B: PUBACK for broker-to-device hop
D->>D: Callback copies/enqueues message
D->>D: Application validates and handles command
D->>B: PUBLISH response on response topic
B->>G: PUBLISH matching response
```
There are separate MQTT delivery hops. A `PUBACK` on the backend's publication confirms the QoS 1 exchange with the broker; it is not a device response. The response is a new application publication. Similarly, receipt by a Go callback does not prove a database write or physical device action.
📚 **Covered in:** [Lesson 4](04-publish-subscribe-simulator.md) implements message exchange. [Lesson 5 — Contracts](05-contracts-validation.md) validates topics and payloads; [Lesson 5A](05a-backend-message-processing.md) turns validated heartbeats into an in-memory device view; [Lesson 6 — Concurrency](06-concurrency-cancellation.md) covers callbacks, queues, and acknowledgement timing; [Lesson 7 — Command/response](07-command-response.md) tracks device results; [Lesson 8 — HTTP commands](08-http-commands.md) exposes the flow to an API caller.
---
## 🔄 6. Recover and stop
**❓ Question:** **What should happen if the broker or network disappears while the backend process remains alive?**
**💬 Answer:** The client can reconnect, but the application must ensure needed subscriptions are present. Offline delivery depends on session settings, QoS, expiry, and broker state, so reconnect alone does not guarantee that every message is recovered.
AutoPaho can reconnect according to its lifecycle policy. After a connection comes back, the application must ensure required subscriptions are present—either because the broker resumed a session or because the client re-subscribed. In this lab, Lesson 4's helper explicitly subscribes again on connection-up. Whether offline publications arrive depends on MQTT session settings, QoS, broker state, and expiry; reconnect alone does not promise recovery of every message.
On shutdown, the application cancels its root context, stops background work, and requests a bounded disconnect. Clean MQTT shutdown, broker session deletion, and completion of business work are different events.
```mermaid
stateDiagram-v2
[*] --> Configured
Configured --> Connecting
Connecting --> Connected: accepted CONNACK
Connecting --> Retry: connect error / rejected attempt
Retry --> Connecting: AutoPaho retries
Connected --> Subscribing: connection-up handler
Subscribing --> Ready: acceptable SUBACK
Ready --> Retry: connection lost
Ready --> Stopping: cancellation signal
Stopping --> [*]: bounded disconnect
```
📚 **Covered in:** [Lesson 2](02-go-connection.md) introduces reconnect and bounded shutdown; [Lesson 4](04-publish-subscribe-simulator.md) restores subscriptions after connection-up; [Lesson 6](06-concurrency-cancellation.md) coordinates worker draining and shutdown. [Lesson 9](09-sessions-recovery.md) measures session recovery, and [Lesson 10](10-expiry-presence-state.md) covers expiry and presence.
---
## 🗺️ Where each part is covered
| Lifecycle area | Lessons | Evidence to understand |
|---|---|---|
| Broker setup, credentials, and topic permissions | [1](01-lab-setup.md), [11](11-security-boundaries.md), [11B](11b-dynamic-security.md) optional | Mosquitto owns broker authentication and topic ACL enforcement; optional live per-device provisioning |
| Go client configuration | [2](02-go-connection.md), [3](03-typed-configuration.md) | Effective settings, identity, validation, and precedence |
| CONNECT, CONNACK, reconnect, and shutdown | [2](02-go-connection.md), [4](04-publish-subscribe-simulator.md), [9](09-sessions-recovery.md) | What connection acceptance proves and what must be restored |
| Topic names, filters, wildcards, and routing | [1](01-lab-setup.md), [4](04-publish-subscribe-simulator.md), [16](16-mqtt5-controls.md) | Concrete topic names, `+`/`#` filter matching, broker routing, and subscription options |
| Subscriptions and broker routing | [4](04-publish-subscribe-simulator.md), [9](09-sessions-recovery.md), [16](16-mqtt5-controls.md) | SUBACK results, matching filters, session restoration, shared subscriptions, and options |
| QoS 0/1/2, duplicates, and acknowledgement scope | [4](04-publish-subscribe-simulator.md), [6](06-concurrency-cancellation.md), [7](07-command-response.md), [16](16-mqtt5-controls.md) | Per-hop packet evidence, DUP/retries, and why QoS does not make business effects exactly once |
| Topic and payload validation in Go | [5](05-contracts-validation.md) | `ParseTopic` checks topic shape and kind; `Decode` validates JSON, schema, route/body device identity, and message values before handling |
| Application state after validation | [5A](05a-backend-message-processing.md) | A valid heartbeat updates a simple in-memory device view; freshness is distinct from connection and machine health |
| Callback work, queues, and overload | [6](06-concurrency-cancellation.md) | Admission, acknowledgement timing, bounded capacity, and shutdown |
| Reliable command/response and HTTP | [7](07-command-response.md), [8](08-http-commands.md) | Correlation, idempotency, timeout, and status reporting |
| Sessions, retained state, expiry, and presence | [9](09-sessions-recovery.md), [10](10-expiry-presence-state.md) | Offline delivery prerequisites, retained desired state, message expiry, Will, and heartbeat freshness |
| MQTT 5 flow controls and capabilities | [16](16-mqtt5-controls.md) | Receive Maximum, packet limits, reason codes, aliases, and capability boundaries |
The table points to the lessons that explain and exercise each part of the lifecycle. Lessons 1–11 and 16 contain detailed instructions; Sections 12–15 and 17–18 in [the course plan](../../COURSE.md) are currently guided checklists that extend the story into vending workflows, persistence, process boundaries, and operations.
### What validates a topic?
Lesson 4 introduces `contracts.DeviceTopic`, fleet-wide `contracts.DeviceFilter`, and exact-device `contracts.DeviceSubscription` in `internal/contracts/topic.go`; Lesson 5 adds `contracts.ParseTopic` there and message `Validate` methods in `message.go`. The route rejects malformed paths and selects the expected payload contract, which then checks that its claimed `device_id` matches the topic before application handling.
Those checks do not authorize the publisher. A client with valid credentials could still try to publish to another device's topic, so Mosquitto must enforce a least-privilege ACL for both publish and subscribe operations. Lesson 11 tests that broker boundary and the separate HTTP-user authorization boundary; optional Lesson 11B covers per-device ACL provisioning. Authentication identifies a broker client, ACLs authorize topic operations, and Lesson 5 validates message content. Use all three boundaries.
---
## 🧪 Understanding check
1. What does a successful `CONNACK` prove, and what does it not prove?
2. How are a topic name, a subscription filter, and an ACL rule different?
3. Why does the backend need both a broker subscription and a local receive handler?
4. Which acknowledgement tells the backend that the device application completed a command?
5. What is the difference between backend topic parsing/validation and broker ACL authorization?
6. What is the difference between a client credential and a Client ID in this lab?
> [!answer]- 📋 Answers
>
> **1.** It proves the broker accepted that MQTT connection. It does not prove a subscription was accepted, a device is online, or business work completed.
>
> **2.** A topic name labels one publication route. A filter can match multiple topic names when the broker routes to a subscription; `+` matches one level and `#` matches the remaining levels. An ACL rule permits or denies a client's publish or subscribe operation.
>
> **3.** The broker subscription controls routing; the local handler controls what Paho/the application does with a delivered PUBLISH.
>
> **4.** None of the MQTT packet acknowledgements proves application completion. The device must publish an application-level response, and the backend must validate it.
>
> **5.** Backend validation checks message shape and consistency after delivery. Broker ACLs decide whether that client may publish or subscribe to the topic at all. Payload validation does not replace authorization.
>
> **6.** Credentials authenticate a client and select its ACL identity here. Client ID identifies the MQTT connection/session and can be configured as an ACL input, but it is not inherently authentication.
---
## 🔗 Related lessons
- [Lesson 1 — Isolated lab setup](01-lab-setup.md)
- [Lesson 2 — Go connection](02-go-connection.md)
- [Lesson 3 — Typed configuration](03-typed-configuration.md)
- [Lesson 4 — Publish, subscribe, simulator](04-publish-subscribe-simulator.md)
- [Course index](../../COURSE.md)