Building distributed applications is a tough job! To get it right you will need lots of tools just to get started. Think about availability, scalability, service discovery, data consistency, developer productivity... Now you have chosen some kind of a container-orchestration system like Kubernetes (K8s) to address some of the challenges. At that point you have alleviated some problems but you have created a new set of issues like for example architectural complexity, operational complexity, configuration complexity and conceptual complexity...
What can we do to help enterprise developers build distributed applications? We already utilize ready-to-use code and patterns as libraries or frameworks. Can't we do the same with distributed apps?
What is Dapr?
The official description goes like this:
Dapr is a portable, event-driven runtime that makes it easy for developers to build resilient, microservice stateless and stateful applications that run on the cloud and edge and embraces the diversity of languages and developer frameworks.
A lot of things are going on in there.
Dapr is a runtime that lets you use some of the best practices out of the box. I think of it as a library of reusable patterns when it comes to microservices and distributed applications. Just like when you have to deal with resiliency and transient-fault-handling in .NET, you will most likely end up using some library like Polly.
In the same way Dapr consists of a set of building blocks and each of them is an API accessible by either HTTP or gRPC that provides you with some kind of an added value like an API for publish/subscribe, API for state management, etc.
Dapr is language and platform agnostic and you don't need any special libraries as it's one HTTP/gRPC call away. Having said that, Dapr also provides several SDKs to make it first-class citizen of the language of your choice. You can learn more about the .NET SDK on Github. Dapr can run anywhere you want - any cloud, on-premises, on the edge, inside Kubernetes or outside.
The building blocks in Dapr essentially expose the API that you work with. But the underlying functionality is performed by one or more components that you configure. All components are pluggable so that you can swap out one component with the same interface for another. For example, if you use Redis as a state store, you can substitute it with Cosmos DB just by changing the definition of your state component. Having applied then new component definition, all requests for state store will go to Cosmos DB.
Components have the following definition in YAML:
apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: <NAME> spec: type: state.<TYPE> metadata: - name:<KEY> value:<VALUE> - name: <KEY> value: <VALUE>
The building blocks provided by Dapr are the following at the time of writing this are:
This puts the burden of service discovery on the Dapr runtime. Resilient service-to-service invocation enables method calls, including retries, on remote services wherever they are running in the supported hosting environment. You have to just issue a request to the /invoke endpoint, specify the appId and the method of the target application, and then Dapr will forward the response back to you. Just like a reverse proxy!
This building block API is used for storing, retrieving and deleting the key/value pairs that represents the state of your services. There are state store components for different data stores such as Redis, Azure CosmosDB, SQL Server, AWS DynamoDB, GCP Cloud Spanner and Cassandra.
Publish and subscribe messaging between services
Publishing events and subscribing to topics is key for some systems. By using this building block, you abstract the usage of services such as Redis, Azure Service Bus, RabitMQ, and NATS. Dapr guarantees At-Least-Once message delivery and will ensure the delivery will hit a single instance of your app in case it's running on multiple.
Event-driven Resource Bindings
You can have an event-driven architecture designed for scale and resiliency by sending and receiving events to and from any external resources such as databases, queues, cloud-services, file systems, blob stores, webhooks, etc.
For example, your code can be triggered by a message coming from Azure Event Hub and then you can write some data to Azure Cosmos DB. But you don't have to employ the concepts of the SDK of the source/target service in your code. As already mentioned, everything happens via HTTP or gRPC.
The actor pattern is a computational model for concurrent or distributed systems in which a large number of these actors can execute simultaneously and independently of each other. Actors can communicate with each other and they can create more actors. Maybe you have used Akka, Service Fabric Reliable Actors or even Orleans to achieve this.
Dapr provides an actor implementation which is based on the Virtual Actor pattern which means that the actors' lifetime is not tied to their in-memory representation. Actors do not need to be explicitly created or destroyed. When actors are not in use they are garbage collected. Actors can schedule periodic work by registering either timers or reminders. In fact, Dapr C# actors are based on Service Fabric Reliable Actors.
This building block is also based on State components.
Distributed tracing collects and aggregates trace events, metrics and performance numbers between Dapr instances. It allows you to trace the entire call chain across multiple services. Dapr uses OpenTelemetry (previously known as OpenCensus) for distributed traces and metrics collection. OpenTelemetry supports various backends including Azure Monitor, Datadog, Instana, Jaeger, SignalFX, Stackdriver, Zipkin and others.
Dapr offers developers a consistent way to extract application secrets, without needing to know the specifics of the secret store being used. Secret stores are components in Dapr.
You can reference secrets stored in a secret store from other components. For example, within the definition of your Redis state component instead of providing the plaintext credentials, you can reference them by names under which they are saved in the secret store. Or you can access secrets from your applications by calling the /secrets endpoint of the Dapr API.
Where can I run Dapr?
Dapr exposes its APIs in a sidecar architecture, either as a container or as a process, and by not requiring the application code to include any Dapr runtime code. This makes integration with Dapr easy from other runtimes. It currently supports two modes: Standalone (self-hosted) and Kubernetes.
In standalone mode Dapr runs as a separate process from which your service code can call via HTTP or gRPC.
In this mode you will need to have Docker installed locally as Dapr will create a Redis container that will serve as your default state store and pub/sub message bus for components.
Dapr natively supports Kubernetes. It can run anywhere where K8s is hosted. In Kubernetes mode, Dapr runs as a side-car container with the application container in the same pod.
Deploying and running a Dapr enabled application into your K8s cluster is a simple as adding a few annotations to the deployment scheme. To give your app an ID and port known to Dapr, turn on tracing information and launch the Dapr sidecar container, you annotate your Kubernetes deployment template like this:
annotations: dapr.io/enabled: "true" dapr.io/id: "nodeapp" dapr.io/port: "3000" dapr.io/config: "tracing"
kubectl apply you'll notice that the pod for your app run two containers.
How to get started?
You can follow the Getting Started guide on dapr/docs.
Support for ASP.NET Core
I have already mentioned the .NET SDK for Dapr.
When it comes to ASP.NET Core, Dapr provides integration with controllers, so that you can access the state store in action methods or you can subscribe to events. There is Cloud Events model binding and Actors support.
How to debug?
Debugging distributed apps is painful. There is an extension for Visual Studio Code helps with the coordination of the debugger with Dapr runtime. At the time of writing it only works for apps running in standalone mode of Dapr. The support for Azure Kubernetes Service with Dev Spaces will come quite handy but it's not there yet.
First you have to configure the debugger to work with Dapr. Hit Ctrl + Shift + P and search for Dapr: Scaffold Dapr Tasks. It will ask for the name of the Dapr application and the port it listens on. This will generate proper Visual Studio Code launch.json configuration file for your project. You can now hit F5 and Dapr will start the app for you in Debug mode.
Then right click your application and hit Invoke GET/POST Application Method. Them VS Code will ask you to provide the name of the application method and some payload:
Where can I find more information?
The documentation of Dapr is on GitHub:
Dapr is currently in alpha release. I would love to know what you think about it and how it meets (or not) your distributed application development needs!