Our Blog

Hands on with the Distributed Application Runtime (Dapr)

Written by Tyler Dueck | Oct 12, 2023 4:23:54 PM

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.

 

Building Blocks

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?

Dapr Integration Paths

I discovered quickly that there are several ways to integrate with Dapr.

The first is simply to run applications inside of the Dapr runtime unchanged. While existing API endpoints can be executed normally, the benefit of this integration path is that it exposes additional identical endpoints via Dapr. By configuring apps to call the Dapr exposed endpoints instead, apps are automatically granted distributed tracing through the Dapr platform (and might leverage additional Dapr benefits, though this is where I'm fuzzy). This is a safe and easy starting path to integrate apps 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.

Experience with the Dapr .NET 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.

Some lessons learned:

1. SDK integration is preferred for its rich integration, but then all resource access is via the Dapr SDK which some teams may find invasive.
  • Alternatively: Dapr can communicate via direct HTTP and gRPC requests. This is treated as a first-class integration option in the documentation, facilitating easier debugging, automation, and cross-platform use of Dapr.
  • Both SDK and direct protocol integration can be used together. Developers don't need to choose one or the other.

2. Dapr components are simple YAML files. Easy to build.
  • Pub/sub subscribers can be defined declaratively in YAML files.
  • Alternatively, the SDK can register subscribers programmatically.

3. Dapr requests expect HTTP POST endpoints to call endpoints. Pub/Sub events call HTTP POST on the target API. I like this. It's simple and endpoints are automatically documented with OAS if this is enabled on the .NET project.
  • Issue: If body model types do not match what Dapr is sending to the application, logs may indicate HTTP 400 from Dapr to the application's API endpoint due to ASP.NET failed model validation.
  • Resolution: Potential incorrect model type used as input. In C#, this can be diagnosed by using [FromBody] object bodyuntil developers have identified the proper data type to use. (The data type is likely enveloped.)


5. DaprClient .NET SDK does not support registration of multiple DaprClient instances in Dependency Injection. It should reference the application's own sidecar only.

  • DaprClient will automatically find other Dapr sidecars and invoke them using the Service Invocation building block without the need to register multiple DaprClient instances.
  • DaprClient should be singleton. It's thread safe.
  • Each app using Dapr needs its own Dapr sidecar instance and a unique port.

6. Tooling continues to change rapidly and many Internet blogs/resources reference deprecated options.
  • Dapr official documentation is accurate, up to date, and correct.
  • Dapr cli is helpful to notify developers when they use deprecated parameters and recommends the correct parameter instead. Developers should read cli logs!

7. Pub/Sub component files should include scopes.

8. Developers should always set the Dapr port explicitly when calling the Dapr cli.
  • If unset, these ports appear to be randomly set on every execution, contrary to documentation which (incorrectly) suggests defaults are used when unspecified.


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.


10. Debugging an app running in the Dapr platform is tricky. Visual Studio Attach to Process does not work!

11. Configuring launchSettings.json works with Visual Studio with some success, very similar to the VSCode debugging option. I’m still finding debugging in VS tricky though I’ve had some success with this link.

Summary

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

Tyler first discovered his passion for software at the age of 6 when he learned about DOS and GW BASIC. Many years later, he turned this passion into a hobby, and then into a career of two decades professionally creating technology and process-based business solutions for clients. He is an excellent communicator and motivated problem-solver, who imparts leadership, a positive attitude, and a passion for software development to the teams he is a part of. Tyler has significant experience leading both business and technical teams through the development and deployment of enterprise level solutions.