This is the first part of a series centered around a sample application I made for tracking simulated vehicles on a map, using Azure services. You can see the current version of the app in my GitHub:

In the first part, we will look at the architecture for the sample app and its components.

Architecture diagram

Architecture diagram for the sample app


Container App

This is where we will run the .NET Console app that acts as a simulator. I chose Container Apps for this purpose as it is quite easy to manually scale to as many instances as needed to produce adequate load for the system. The ability to scale to zero is also quite nice.

The simulator chooses from a set of pre-defined routes and starts moving through it with a default speed of 50 kilometers per hour.

Device Provisioning Service

We want each simulated device instance to have its own identity in the IoT Hub. DPS makes this quite convenient by allowing the devices to self-provision. When the instances start, they will first contact DPS, which then creates a device for them in the IoT Hub and enables the instance to connect.

IoT Hub

Simulator device instances will connect here after provisioning with DPS. Location update events are routed to an Event Hub. A device twin is created for each device instance, and through this we can send configuration updates to the instances while they are running.

Event Hub

This is where we collect the location update events. We could alternatively use the Event Hub compatible endpoint in IoT Hub directly and leave out this component. The advantage of having it is that if in the future we need to receive similar events from another data source, we can just send events to this same Event Hub, and nothing else needs to change. Another advantage is that we can more easily control data access through Azure RBAC as read permissions in IoT Hub grant access to other data than just the telemetry.

Azure Data Explorer

Used as the store for historical data, receives data from Event Hub. Depending on use case, ADX might be really overkill. It also isn't very cheap, but does allow some nice things:

  1. Absolutely massive capability for data processing and storage
  2. Querying for data is very fast
  3. Can visualize geospatial data
  4. Data retention can be set so we automatically cleanup old data

Alternatively we could use Cosmos DB or SQL Database, both of them have the ability to process geospatial data. Disadvantage of Cosmos DB (and SQL to an extent) is that indexing and how the data are modeled needs to be very carefully planned. Most data models in Cosmos DB are either write- or read-optimized, but noth both. In an IoT use case, we typically need a write-optimized model as we are writing a lot more data than reading it. Then we need to either accept the lower read performance or duplicate the data in a more read-optimized model.

SQL Database

Used for storing latest location data, as well as checking geofences. We can quite nicely write latest locations with optimistic concurrency checks, and since SQL DB does support geospatial data, we can conveniently check if the latest location falls inside a geofence, and trigger events from this.

Function App

The core of the application that serves multiple purposes:

  • Hosts front-end HTML/CSS/JS files
  • Back-end API for front-end
    • Get access token for Azure Maps
    • Get geofences
    • Get geofence events for a device
    • Get historical locations for a device
    • Update device parameters
    • Connect to SignalR Service
  • Handles location update events from Event Hub
    • Update latest locations
    • Check geofences
  • Sends events to front-end through SignalR Service
  • Receives messages from front-end through SignalR Service

Alternatives for this component (or pieces of it) are many in Azure. Processing Event Hub events with Functions is quite convenient and putting the rest of the pieces in here somewhat simplifies the architecture.

SignalR Service

We want to receive real-time location updates as well as geofence entry/exit events in the front-end application. Polling for these events would be inefficient; using Web Sockets instead allows the front-end to receive events in real-time. We don't need SignalR Service for Web Sockets, an App Service can handle that as well. But since we use a Function App that can scale in and out as needed, we don't have a stable back-end that the Web Sockets could connect to. SignalR Service solves this by:

  1. Keeping a persistent connection to the front-ends
  2. Providing an API for the Function App through which we can send events
  3. Calling back to the Function App through upstreams when front-ends send messages

Azure Maps

We want to show the simulated devices on a map view in the front-end, so we chose Azure Maps to provide the map tiles. The experience with Azure Maps that I've had is that its tiles are of decent quality and having all of the billing in one place is convenient. Azure Maps does provide other features as well, like reverse geocoding (find address from coordinates), but we don't need those in this sample application.

Next article

In the next part, we will look more deeply at the device simulator and how it functions. Thanks for reading!