| Attribute | Details |
|---|---|
| Dapr runtime version | v1.15.5 |
| Dapr.NET SDK version | v1.15.4 |
| Dapr CLI version | v1.15.1 |
| Language | C# |
| Platform | .NET 8 |
| Environment | Self hosted or Kubernetes |
This repository contains a sample application that simulates a traffic-control system using Dapr. For this sample I've used a speeding-camera setup as can be found on several Dutch highways. A set of cameras are placed at the beginning and the end of a stretch of highway. Using data from these cameras, the average speed of a vehicle is measured. If this average speed is above the speeding limit on this highway, the driver of the vehicle receives a fine.
This is an overview of the fictitious setup I'm simulating in this sample:

There's 1 entry-camera and 1 exit-camera per lane. When a car passes an entry-camera, the license-number of the car and the timestamp is registered.
When the car passes an exit-camera, this timestamp is also registered by the system. The system then calculates the average speed of the car based on the entry- and exit-timestamp. If a speeding violation is detected, a message is sent to the Central Fine Collection Agency (or CJIB in Dutch). They will retrieve the information of the owner of the vehicle and send him or her a fine.
In order to simulate this in code, I created the following services:

/entrycam and /exitcam./collectfine for collecting fines./vehicleinfo/{license-number} for getting the vehicle- and owner-information of speeding vehicle.The way the simulation works is depicted in the sequence diagram below:

/entrycam endpoint of the TrafficControlService./exitcam endpoint of the TrafficControlService (containing the license-number generated in step 1, a random exit-lane (1-3) and the exit timestamp)./collectfine endpoint of the FineCollectionService. The request payload will be a SpeedingViolation containing the license-number of the vehicle, the identifier of the road, the speeding-violation in KMh and the timestamp of the violation./vehicleinfo/{license-number} endpoint of the VehicleRegistrationService with the license-number of the speeding vehicle to retrieve its vehicle- and owner-information.All actions described in this sequence are logged to the console during execution so you can follow the flow.
This sample uses Dapr for implementing several aspects of the application. In the diagram below you see a schematic overview of the setup:

Here is the sequence diagram again, but now with all the Dapr building blocks and components:

In self-hosted mode everything will run on your local machine. To prevent port-collisions, all services listen on a different HTTP port. When running the services with Dapr, you need additional ports for HTTP and gRPC communication with the sidecars. By default these ports are 3500 and 50001. But to prevent confusion, you'll use totally different port numbers in the assignments. The services will use the following ports:
| Service | Application Port | Dapr sidecar HTTP port | Dapr sidecar gRPC port |
|---|---|---|---|
| TrafficControlService | 6000 | 3600 | 60000 |
| FineCollectionService | 6001 | 3601 | 60001 |
| VehicleRegistrationService | 6002 | 3602 | 60002 |
The ports can be specified on the command-line when starting a service with the Dapr CLI. The following command-line flags can be used:
--app-port--dapr-http-port--dapr-grpc-portSome antivirus software blocks mDNS (we've actually encountered this with Sophos). mDNS is used for name-resolution by Dapr when running in self-hosted mode. Blocking mDNS will cause issues with service invocation. Dapr can also use Consul for name-resolution. This is used by default in this sample application.
You configure Dapr to use Consul by specifying this in a Dapr config file. The src/dapr/config folder contains a config file that configures Dapr to use Consul. You use the --config command-line argument to specify the config file to use (this is included in the instructions below). You can verify whether Dapr uses Consul for name-resolution by inspecting the logging. You should find a line in the Dapr logging that indicates the naming service used:
...
INFO[0000] Initialized name resolution to consul app_id=vehicleregistrationservice instance=192.168.2.16 scope=dapr.runtime type=log ...
...
Execute the following steps to run the sample application in self hosted mode:
src/infrastructure folder of this repo.start-all.ps1 or start-all.sh script. This script will start Consul, Mosquitto (MQTT broker), RabbitMQ (pub/sub broker) and Maildev. Maildev is a development SMTP server that does not actually send out emails (by default). Instead, it offers a web frontend that will act as an email in-box showing the emails that were sent to the SMTP server. This is very convenient for demos of testscenarios.stop-all.ps1 or stop-all.sh script.Open a new command-shell.
Change the current folder to the src/VehicleRegistrationService folder of this repo.
Execute the following command (using the Dapr cli) to run the VehicleRegistrationService:
console
dapr run --app-id vehicleregistrationservice --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 --config ../dapr/config/config.yaml --resources-path ../dapr/components dotnet run
Alternatively you can also run the
start-selfhosted.ps1orstart-selfhosted.shscript.
Open a new command-shell.
Change the current folder to the src/FineCollectionService folder of this repo.
Execute the following command (using the Dapr cli) to run the FineCollectionService:
console
dapr run --app-id finecollectionservice --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --config ../dapr/config/config.yaml --resources-path ../dapr/components dotnet run
Alternatively you can also run the
start-selfhosted.ps1orstart-selfhosted.shscript.
Open a new command-shell.
Change the current folder to the src/TrafficControlService folder of this repo.
Execute the following command (using the Dapr cli) to run the TrafficControlService:
console
dapr run --app-id trafficcontrolservice --app-port 6000 --dapr-http-port 3600 --dapr-grpc-port 60000 --config ../dapr/config/config.yaml --resources-path ../dapr/components dotnet run
Alternatively you can also run the
start-selfhosted.ps1orstart-selfhosted.shscript.
Open a new command-shell.
Change the current folder to the src/Simulation folder of this repo.
Execute the following command to run the Camera Simulation:
console
dotnet run
You should now see logging in each of the shells, similar to the logging shown below:
Camera Simulation:

TrafficControlService:

FineCollectionService:

VehicleRegistrationService:

To see the emails that are sent by the FineCollectionService, open a browser and browse to http://localhost:4000. You should see the emails coming in:

This repository also contains a graphical version of the Camera Simulation:

The cars are all driving at different speeds and the simulation uses different "personas" to simulate drivers that tend to drive faster than others. They will also try to overtake where possible. These drivers are most likely to get a speeding ticket.
The simulation runs in a web-browser. In order to start the web-application host and run the simulation, execute the following steps:
Open a new command-shell.
Change the current folder to the src/VisualSimulation folder of this repo.
Execute the following command to run the Visual Camera Simulation:
console
dotnet run
Open a browser window and navigate to http://localhost:5000.
Use the arrow keys to scroll and zoom in/out.
The TrafficControlService has an alternative implementation based on Dapr actors.
The TrafficController in the TrafficControlService has 2 implementations of the VehicleEntry and VehicleExit methods. The top two methods contain all the code for handling vehicle registrations and storing vehicle state using the state management building block. The bottom two methods use a VehicleActor that does all the work. A new instance of the VehicleActor is created for each registered vehicle. In stead of using the state management building block, the actor uses its built-in StateManager.
You can find the code of the actor in the file src/TrafficControlService/Actors/VehicleActor.cs.
To use the actor based implementation, uncomment the statement that defines the USE_ACTORMODEL symbol at the top of the controller:
#define USE_ACTORMODEL
Now you can restart the application just as before. The behavior is exactly the same, but you will see that the logging of the TrafficControlService will be emitted by the VehicleActor:

Execute the following steps to run the sample application on Kubernetes:
Make sure you have installed Dapr on your machine on a Kubernetes cluster as described in the Dapr documentation.
Open a new command-shell.
Change the current folder to the src/k8s folder of this repo.
Run the build-docker-images.ps1 script. This script will build Docker images for all the services and a custom Mosquitto image used when running on Kubernetes.
Execute the start.ps1 script. All services will be created in the dapr-trafficcontrol namespace.
You can check whether everything is running correctly by examining the container logs. There are several ways of doing that.
$ claude mcp add dapr-traffic-control \
-- python -m otcore.mcp_server <graph>