1. MQTT Is a Communication Model, Not a Feature
A common mistake is to think of MQTT as a “lighter HTTP.”
That assumption breaks quickly.
HTTP assumes:
- Stable connectivity
- Synchronous request–response
- Short-lived interactions
MQTT assumes the opposite:
- Disconnections are normal
- Messages are asynchronous events
- Systems are long-running
In MQTT:
- Topics are streams of intent, not endpoints
- Brokers route, they don’t interpret
- Messages describe state changes, not function calls
Once I stopped forcing HTTP mental models onto MQTT, many design problems disappeared.
2. What Mosquitto Does — and Intentionally Does Not Do
Mosquitto is minimal by design. It doesn’t try to solve your business problems, and it doesn’t hide protocol behavior behind abstractions. Every decision — persistence, retain, QoS, sessions — is explicit.
Mosquitto’s responsibility is clear and narrow:
- Accept connections
- Authenticate clients
- Route messages
- Apply QoS and persistence rules
What it intentionally does not do:
- Manage device state
- Execute workflows
- Understand business semantics
Service should not depend on the broker “understanding” commands, that coupling will surface later as rigidity or hidden bugs.
3. The Real Problems in Device Communication
Before thinking about topics or configuration, I found it useful to enumerate the real problems:
Device identity
- Who is this device?
- What is it allowed to publish or subscribe to?
Online vs offline
- A TCP connection does not mean availability
- Offline is a valid, expected state
Delivery vs execution
- Was the command received?
- Was it executed?
- Did the device reboot in between?
Uncertainty
- Messages may be duplicated
- Messages may arrive late or out of order
- Devices reconnect and replay state
MQTT does not remove these problems — it exposes them.
4. Topic Design as System Design
Early on, I tried to “simplify” by using fewer topics.
That turned out to be a mistake.
Now I treat topic design as part of system modeling:
-
Command topic
Service → Device
Represents intent -
Acknowledgement topic
Device → Service
Confirms receipt -
Result topic
Device → Service
Confirms execution -
Status / heartbeat topic
Device → Service
Describes current state
Each topic answers a different question.
Combining them saves lines of configuration but costs clarity.
5. QoS, Retain, and Persistence: Hard Choices
MQTT gives you powerful tools, but they are not free.
Retain
- Never retain commands
- A retained command can execute hours or days later
- Retain is useful for state, not intent
QoS
- QoS 0: fast, unreliable
- QoS 1: “at least once” — often the sweet spot
- QoS 2: rarely worth the complexity
Persistence
- Persistence protects against broker restarts
- It does not protect against bad semantics
Reliability is not achieved by turning on every option — it comes from aligning configuration with meaning.
6. Treating MQTT as a Concurrent System on the Backend
From the backend’s point of view, MQTT is a concurrent event stream.
That means:
- Messages must be idempotent
- State transitions must be explicit
- Timeouts must be intentional
I found it helpful to model command handling as a state machine:
- Created
- Sent
- Acknowledged
- Executed
- Timed out or failed
This framing aligns well with how distributed systems behave under partial failure.
7. Clear Boundaries: Broker vs Backend
Using Mosquitto made responsibility boundaries obvious:
- Broker (Mosquitto)
- Transport
- Routing
- Delivery semantics
- Backend services
- Authorization
- State storage
- Command lifecycle
- Business logic
By keeping Mosquitto “dumb,” the backend becomes easier to test, reason about, and evolve.
8. Mistakes I Made While Learning MQTT
Some mistakes only become visible in production-like conditions:
- Treating MQTT like RPC
- Trusting online status too much
- Overusing retain
- Assuming messages arrive exactly once
- Ignoring reconnect semantics
Most MQTT issues are not obvious bugs — they are design mismatches that surface slowly.
