A layered model that separates concerns, enables independent scaling, and prevents the monolithic-device-cloud anti-pattern.
Summary
A cold-chain logistics platform had built everything in a single Azure Functions app: ingestion, command routing, dashboard API, and alerting. It worked great at 500 trackers. At 5,000, a temperature alert took 90 seconds to reach the dashboard because a batch export function was consuming every execution slot. The problem was not the Azure services. It was that every concern shared the same scaling boundary.
IoT architectures fail when responsibilities leak across boundaries. This post presents a five-layer model — Device, Edge/Gateway, Ingestion, Business Logic, and Presentation — that separates concerns, enables independent scaling, and gives you a vocabulary for deciding where functionality should live. Each layer maps to specific Azure services, with real examples of what breaks when a layer is missing or collapsed.
The Structural Problem of IoT
Every IoT system faces the same structural problem: physical things need to talk to cloud software, and between those endpoints lies a gauntlet of protocol translations, security boundaries, scale challenges, and processing requirements. Without a shared mental model, architecture discussions collapse into technology arguments. “Event Hubs or IoT Hub?” becomes a flame war instead of a design question.
The layered model resolves these debates by answering a higher-order question first: “Which layer does this responsibility belong to?” The service choice follows from the layer assignment.
Skipping a layer does not eliminate the problems it solves; it pushes them somewhere harder and more expensive. Bypass the edge layer and protocol translation lands in device firmware (hard to update) or cloud ingestion (expensive at scale). Collapse business logic into ingestion and you get a monolithic processor that does everything and scales nothing. We saw this when a smart-building startup ran a custom protocol translator inside every one of 200 BACnet sensors; when the translation logic needed to change, twelve sensors sealed inside ceiling tiles never got the firmware update.
The Five Layers
Presentation Layer
Dashboards, APIs, Mobile Apps, ERP Integration
↑
Business Logic Layer
Stream Processing, Rules, ML, Digital Twins, Alerts
↑
Ingestion & Routing Layer
IoT Hub, DPS, Event Hubs, Message Routing
↑
Edge / Gateway Layer
IoT Edge, Protocol Translation, Local Buffering
↑
Device Layer
Sensors, Actuators, Controllers, Firmware
Notice the funnel. Millions of devices consolidate into thousands of gateways, funnel through a handful of managed service instances, get domain-specific processing, and finally serve a comparatively small number of human users. That funnel shape has real implications for scaling, failure isolation, and operations.
Layer 1: Device — Sense, Act, Report
The device layer is the most constrained and least changeable. Firmware updates are possible but expensive and risky, so every gram of complexity you keep out of it is complexity you can iterate on faster above. A well-designed device layer does exactly three things: it reads sensors accurately, communicates reliably using a well-defined message schema, and executes commands safely. Everything else is a candidate for delegation upward.
The Message Schema Contract
The most important decision in this layer is the message schema — the format of telemetry flowing upward. It is the contract between the device and everything downstream; change it without coordination and you break every consumer. Here is a schema we use at Mesh for HVAC sensors:
{
"ts": 1705312800000,
"dev": "ahu-001-sensor-b",
"readings": [
{ "k": "temp_discharge", "v": 72.3, "u": "f" },
{ "k": "humidity", "v": 45.2, "u": "rh" },
{ "k": "fan_speed", "v": 1200, "u": "rpm" }
]
}
It is deliberately compact: abbreviated keys cut IoT Hub message costs at scale, batched readings reduce message count, and Unix-epoch-millisecond timestamps give unambiguous ordering. Define the schema once, version it, and treat it as a public API. A change is a coordinated rollout, not a firmware push and a prayer.
When it goes wrong: We reviewed firmware that embedded alert thresholds directly in device code. Changing one threshold from 78°F to 76°F meant compiling, testing, and deploying a new image to 3,000 devices — two weeks and a maintenance window for what should have been a config update. The device reads and reports; the cloud decides.
Layer 2: Edge/Gateway — Aggregate, Translate, Buffer
The edge layer is optional — many solutions connect devices straight to the cloud — but when you need it, nothing else substitutes. It earns its place when you need protocol translation (devices speak Modbus, OPC-UA, BACnet, or CAN bus; the cloud speaks MQTT or AMQP), local buffering through connectivity drops, local filtering or ML inference to cut cloud volume, or network segmentation between OT and IT.
A Real Buffering Calculation
Consider a gateway aggregating 200 sensors, each sending a 200-byte message every 5 seconds:
| Metric | Value |
|---|---|
| Messages per second | 200 sensors ÷ 5 seconds = 40 msg/s |
| Data per second | 40 × 200 bytes = 8 KB/s |
| Data per hour | 8 KB/s × 3,600 = 28.8 MB/h |
| 24-hour buffer | 28.8 MB × 24 = 691.2 MB |
A gateway with 8 GB of local storage buffers roughly 11.5 days of continuous data, so a weekend cellular outage loses nothing. Without the edge layer, all 200 sensors would each need their own modem and buffer — cost and complexity multiplied by 200.
IoT Edge Module Deployment
Here is a simplified IoT Edge deployment manifest that deploys a custom protocol translator module alongside the built-in routing modules:
{
"modulesContent": {
"$edgeAgent": {
"properties.desired": {
"schemaVersion": "1.2",
"runtime": { "type": "docker" },
"modules": [
"protocol-translator",
"data-aggregator"
]
}
},
"$edgeHub": {
"properties.desired": {
"routes": {
"TranslatorToCloud": "FROM /messages/modules/protocol-translator/outputs/telemetry INTO $upstream",
"AggregatorToCloud": "FROM /messages/modules/data-aggregator/outputs/batched INTO $upstream"
}
}
},
"protocol-translator": {
"settings": {
"image": "mesh.azurecr.io/protocol-translator:2.1.0",
"createOptions": "{}"
}
}
}
}
Routing is declarative: you define where messages flow in the manifest, not in code, which decouples the edge modules from cloud ingestion.
Layer 3: Ingestion & Routing — Receive, Authenticate, Route
The ingestion layer is the front door, with two jobs in natural tension: accept messages as fast as devices send them (throughput) and reject unauthorized sources (security). IoT Hub is the canonical service — it handles authentication via DPS-provisioned identities, message routing, and cloud-to-device communication. Event Hubs takes over when you need raw throughput beyond IoT Hub’s limits or are ingesting from non-device sources.
IoT Hub Message Routing Configuration
One of IoT Hub’s most powerful and underused features is message routing: direct different message types to different downstream processors with no code.
{
"endpoint": {
"eventHubs": [
{
"name": "telemetry-events",
"connectionString": "Endpoint=sb://mesh-ns.servicebus.windows.net/..."
}
],
"serviceBusQueues": [
{
"name": "alerts-queue",
"connectionString": "Endpoint=sb://mesh-ns.servicebus.windows.net/..."
}
]
},
"routes": [
{
"name": "all-telemetry",
"source": "DeviceMessages",
"condition": "eventType = 'telemetry'",
"endpointNames": ["telemetry-events"],
"isEnabled": true
},
{
"name": "critical-alerts",
"source": "DeviceMessages",
"condition": "eventType = 'alert' AND severity = 'critical'",
"endpointNames": ["alerts-queue"],
"isEnabled": true
}
],
"fallbackRoute": {
"name": "$fallback",
"endpointNames": ["telemetry-events"]
}
}
Telemetry flows to Event Hubs for stream processing; critical alerts go straight to a Service Bus queue for immediate handling. No custom code required.
When it goes wrong: A team routed every IoT Hub message to one Azure Function handling alerting, storage, dashboards, and analytics. When volume doubled during a launch, the function hit its execution-time limit, alerts stopped firing, and the dashboard went stale. The fix was not a bigger function — it was routing, giving each consumer its own scaling boundary.
Layer 4: Business Logic — Process, Analyze, Decide
This is where raw readings become business value. A temperature of 78.3°F means nothing until it is compared against a threshold, correlated with a machine’s operating profile, and recognized as bearing wear. The layer typically spans several services and latency tiers:
| Capability | Azure Service | Pattern |
|---|---|---|
| Real-time stream processing | Azure Stream Analytics | Hot path: seconds-to-minutes |
| Event-driven processing | Azure Functions | Warm path: minutes-to-hours |
| Batch analytics | Azure Synapse / Databricks | Cold path: hours-to-days |
| Rule evaluation | Stream Analytics / Functions | Threshold, trend, correlation rules |
| Digital twin state | Azure Digital Twins | Graph-based entity modeling |
| ML inference | Azure ML | Anomaly detection, prediction |
The Hot/Warm/Cold Path Model
Not all data needs the same processing speed. Matching cost to urgency is the core discipline of this layer. Take a food-safety reading from a cold-chain shipment:
| Path | What Happens | Latency Target |
|---|---|---|
| Hot (seconds) | Exceeds critical threshold → alert + automated rerouting command | < 30 seconds |
| Warm (minutes) | Trending upward → dashboard update + notify logistics manager | < 5 minutes |
| Cold (hours) | Archived for FDA compliance audit | < 24 hours |
The same telemetry event feeds all three paths, but each processes it differently and serves a different consumer. Build only a hot path and force it to serve all three, and you waste money and make the system harder to operate.
Layer 5: Presentation — Visualize, Command, Integrate
The presentation layer determines whether anyone actually uses the data you collect. A brilliant anomaly detector is worthless if its alert gets lost in a cluttered dashboard operators have learned to ignore. This layer covers dashboards for real-time monitoring (Power BI, Grafana, custom apps), device-management UIs, REST/GraphQL APIs for ERP/CMMS/ticketing integration, and mobile apps for field technicians.
The Dashboard Staleness Problem
The most common operator complaint — “the dashboard shows yesterday’s data” — is rarely a broken pipeline. It is a dashboard querying raw telemetry instead of a pre-aggregated state table. The fix: the hot path writes latest device state to a fast-read store (Cosmos DB, Azure Cache for Redis), and the dashboard does a simple point lookup against that store rather than a time-series aggregation over billions of records. Here is the Stream Analytics query pattern we use to maintain that latest-state table:
SELECT
System.Timestamp AS ProcessingTime,
deviceId,
GetArrayElement(readingArray, 0).v AS temperature,
GetArrayElement(readingArray, 1).v AS humidity
INTO
LatestDeviceState
FROM
TelemetryInput TIMESTAMP BY ts
The dashboard reads this continuously updated table, delivering sub-second updates without touching historical records.
Layer Independence: The Three Questions
The principle behind the model: each layer should be independently deployable, scalable, and observable. Test each one:
| Question | What It Means |
|---|---|
| Can I deploy it without touching others? | A dashboard redesign needs no firmware update; a new alert rule needs no IoT Hub change. |
| Can I scale it independently? | Adding 10,000 devices scales IoT Hub, not the dashboard; 100 new users scale the API, not stream processing. |
| Can I observe its health alone? | Device connectivity metrics are separate from API response times and stream-processor backlog. |
When the answer is “no,” you have coupling — and coupling is the enemy of operational agility.
Key Takeaways
- Layers scale independently. Ingestion scales with device count, dashboards with user count — not the same number.
- Skipping a layer relocates its problems. Bypassing the edge pushes protocol translation into firmware or cloud ingestion.
- Message routing is your first separation. Split telemetry from alerts at IoT Hub before any code runs.
- Hot/warm/cold matches cost to urgency. If no one acts on a data point within 60 seconds, it probably does not belong in the hot path.
- Dashboard staleness is a layer violation. Read from a latest-state table, not raw telemetry.
About the author: Kyle Burns is a Practice Lead and Technical Principal at Mesh Systems and the author of Mastering IoT Solutions on Azure with the Well-Architected Framework. For the full reference architecture with detailed data flows and per-layer pillar analysis, see Chapter 5.
Talk to us
The difference between an IoT platform that scales and one that stalls is where responsibilities live. Mesh Systems designs, builds, and operates layered IoT architectures on Azure that separate concerns cleanly from device to dashboard. If your telemetry is outgrowing a monolith, or a single function is quietly holding your whole system together, let’s pressure-test your architecture before scale forces the issue.