Welcome to "Hands On" a Technical Perspective from our blog series - a digital playground for the truly tech-obsessed. Here at Online, our dedicated team of professionals live and breathe technology. With combined expertise spanning data science, cybersecurity, software development, AI, and everything in between, we seek to elucidate the most complex concepts in simple, digestible terms. We're not just on the cutting edge: here, we sharpen it.
Enjoy today's hands-on approach penned by our long-time friend and talented colleague Tyler Dueck- as he dissects, discusses, and demystifies the topic of Distributed Application Runtime (Dapr)...
One of Online’s values is Forward Thinking: “We are always evolving.”
I recently implemented a small project to explore Dapr, the Distributed Application Runtime. I was interested in Dapr after seeing a note in Azure Container Apps documentation that it was natively supported by the platform. Dapr runs as a sidecar to containers in Azure Container Apps or other container-based systems. Online has used Azure Container Apps with past projects and I was curious about the features that Dapr might enhance our apps now or in the future.
Dapr allows developers to write microservice applications with any language and with a focus on portability by abstracting common "building blocks" through HTTP API’s for service discovery and invocation, state management, pub/sub, bindings, actor model (including timers and reminders), secrets management, configuration, distributed locks, workflows, and cryptography.
Dapr includes observability through distributed tracing, and can handle resiliency with built-in policies for timeouts, retries/back-off, and circuit breakers, and app health-checks. Building blocks are defined as components in YAML, which are loaded by the Dapr command line interface (CLI) during application load. Reviewing the component reference for supported services shows deep maturity in supported platforms across major cloud providers, and provides additional mature options for those running on-prem or for which cloud is not an option.
These building block API abstractions enable developers to focus on the concepts of secrets, state, actors, etc., without introducing additional cloud-service dependencies for each. For instance, by using the secrets management Dapr component, developers no longer need to add hard dependencies to Azure Key Vault in their application code. This provides benefits for mocking secrets and allows different secrets providers to be used for local development vs. production use, rather adding this dependency to a Dapr component YAML file instead. Considering the frequency that vendor libraries may be deprecated or replaced, this has significant benefits for applications that are cloud agnostic, are expected to be long-lived, or might be converted to a new framework in the future.
(This brings flashbacks of asymmetric library upgrades from deprecated Microsoft.Azure.ServiceBus libraries to Azure.Messaging.ServiceBus, also an issue previously with Azure Cosmos DB vs. DocumentDB .NET libraries.)
Arguably, this still trades one dependency for another.
Instead of relying on vendor libraries directly (which may have advanced features and improved debugging capabilities), developers instead would be choosing to add an SDK dependency to an abstraction of the lowest-common-denominator feature set, relying instead on Dapr project contributors to add support for new features as they are provided. Developers need to weigh the benefits of adding a direct resource dependency vs. a Dapr dependency.
Dapr also shares some capabilities common to Service Meshes, though Dapr itself is not a service mesh. It secures service-to-service communication via mTLS encryption, handles service resiliency, and includes automatic distributed tracing and metrics collection.
All these features, when combined, sounds like a dream framework for building apps. So how is it in practice?
I discovered quickly that there are several ways to integrate with Dapr.
The second integration path with Dapr is to leverage Dapr building blocks via raw HTTP or gRPC. These building blocks enable language and service agnostic abstraction of common application elements and cloud services. This aspect of Dapr excites me as I'm a big fan of PowerShell scripting and see potential for building onboarding/deployment/test scripts at a level higher than simply to call individual endpoints. Dapr sidecars always communicate with each other via gRPC.
The third integration path with Dapr is to use an SDK. At time of this article, .NET and Python have the fullest experience, although Java, Go, PHP, and JavaScript have stable SDK's as well. The trade-off is that your apps now have awareness of Dapr as all calls should be directed through the SDK.
The first thing I noticed when using the .NET SDK is that it's syntax is very similar to the Azure Durable Functions/Entities SDK, and that's to be expected. Dapr started as a Microsoft project and was donated to the Cloud Native Computing Foundation. I love Azure Durable Functions/Entities and to abstract state management/Actors away into a familiar HTTP-based platform that can presently support 15 "stable" state stores is freeing.
5. DaprClient .NET SDK does not support registration of multiple DaprClient instances in Dependency Injection. It should reference the application's own sidecar only.
9. Getting started with Dapr assumes developers have Docker installed, though this is an optional dependency, providing rich distributed tracing and pre-configured state management via Redis.
I'm impressed with the Dapr platform, and while I'm not certain that portability is priority for the systems I create, the aspects I tested were well-designed and worked smoothly. Incorporating Dapr into future projects, even for a subset of resources (at least declarative pub/sub registration and actors) would be beneficial. I didn't dive deep into actors, but the inclusion of timers and reminders makes this a strong replacement for Azure Durable Entities in the future. This platform has a sense of maturity to it, and I'm optimistic that I will have opportunities to apply it on future projects.
The debugging procedure is a frustration for me, however. While there's a reasonable path to debug, it’s still rough around the edges. While some sources have shared options around this, I have been unable to repeat them consistently and there’s more I need to learn around debugging with Dapr. This highlights aspects of Dapr that are still "Preview" or lacking in maturity. While there are approaches to mitigate this, such as placing greater emphasis on HTTP integration (over SDK integration), portability, improved test points, and test automation, this is a stumbling block that may interfere with developer productivity in the short-term.
I do not see immature debugging support as showstopper for Dapr, with its already strong emphasis on detailed logging, distributed tracing, and HTTP API's for resource access. These API's are well-documented and can be seen as a set of test-points, adding options for troubleshooting. Maybe this is the true value of portability.
About the Author