In this “Exploring Dapr” series, I’m exploring some of its capabilities and sharing my thoughts on it. Dapr is an event-driven portable runtime for building micro services, for more information I refer to the documentation. Be aware that Dapr is currently under constant evolution, so some of the described behavior or findings might not be accurate anymore in the future.
This blog post is based on Dapr version 0.3 and digs deeper into the publish subscribe integration with Azure Service Bus. It continues on the ASP.NET sample application that is explained in part 2 of this series.
Configure the pub / sub component
When Dapr runs standalone, it discovers the available components in the /Components folder. This is where we have to specify Azure Service Bus as our publish / subscribe message bus. The component is described in YAML, the documentation for Azure Service Bus can be found here. In this case, I only specify the required properties:
apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub-azure-service-bus spec: type: pubsub.azure.servicebus metadata: - name: connectionString value: ###
Test the application
- Start the application again
dapr run --app-id pubsub --app-port 5000 --port 3500 dotnet run
In the background, you’ll notice that the ordertopic – with one subscription – automatically got created on the Azure Service Bus. If you don’t want the application to create topics, you can disable this via the disableEntityManagement property. Remember that you need Manage rights to create topics!
- Invoke the Order API
- You should see that the application still works
- Next to that, you’ll notice that the message got sent over the Service Bus.
Test the reliability
The at-least-once reliability is very important distributed applications. Let’s see if it works like expected.
- Therefore, I update the subscribe method to throw exceptions in 80% of the cases.
public async Task<IActionResult> ProcessOrder([FromBody]Order order) { //Simulate exceptions in 80% of the cases var random = new Random(); if (random.Next(1, 10) > 2) { _logger.LogError($"Order with id {order.id} failed!"); return StatusCode(500); } _logger.LogInformation($"Order with id {order.id} processed!"); return Ok(); }
- In case we invoke now the Order API, you see that retries are automatically kicking in.
- Some messages fail 10 times (which is the default MaxDeliveryCount) and get dead lettered. As always, you need a process in place to monitor and handles these.
- This maxDeliveryCount can be configured in the component YAML description. Be aware that at the time of writing, these parameters are only taken into account when creating a topic subscription if it does not exists. In case the topic subscription already exists, it does not get updated and these values are being ignored. I opened an issue on GitHub that describes this behavior.
Good to see that the Dapr sidecar behaves correctly in error situations! Correct usage of the Azure Service Bus Peek-Lock pattern.
Test with multiple subscribers
Let’s check if the pub / sub works fine with multiple Dapr applications subscribing on the same topic.
- Run Dapr application pubsub1 on HTTP ports 5000 + 3500 in one command prompt
dapr run --app-id pubsub1 --app-port 5000 --port 3500 -- dotnet run --urls http://0.0.0.0:5000
- Run Dapr application pubsub2 on HTTP ports 6000 + 6001 in another command prompt
dapr run --app-id pubsub2 --app-port 6000 --port 6001 -- dotnet run --urls http://0.0.0.0:6000
- Invoke the Order API in one of the two applications
- Because both applications have a different Dapr App Id, each one of them receives a copy of the order.
If both applications would have the same Dapr App Id (which can’t be simulated in the standalone mode), only one of them would receive a copy of the order, because they behave as competing consumers.
Conclusion
In order to switch to my beloved Azure Service Bus, I only had to update the YAML component description, provide the ASB connection string and everything worked as expected. Dapr automatically applies the Peek-Lock pattern in the right way, so reliability is ensured. I don’t need to worry about scalability, because both multiple subscribers and competing consumers are out-of-the-box supported. Without knowing anything about Azure Service Bus, my application already applies the best practices. That’s what Dapr is all about!
I have two important side remarks on Dapr and I think this is common for similar projects. The first one is that you’re typically only using a subset of a specific service, in this case the “common denominator” for all pub / sub engines. A second one is the fact that each service has different limitations, e.g. the message size. ASB is quite strict on this, while Redis allows bigger message sizes. There was already a discussion on this topic in one of the Dapr community calls, curious to see how (and if) this will be tackled.
Cheers
Toon