In a recent blog post, we introduced Dapr (Distributed Application Runtime) and highlighted its potential as a valuable tool for cloud-native applications, in combination with Aspire. This post dives deeper into the inner workings of Dapr, explaining its architecture, components, and how to leverage its capabilities effectively.
Dapr under the hood
As previously mentioned, Dapr (Distributed Application Runtime) is an excellent toolkit for building distributed applications. It offers various components, known as Building Blocks, each serving a specific function. Dapr's strength lies in its language-agnostic nature, allowing developers to create applications in any language of their choice while leveraging as many of Dapr's Building Blocks as needed.
Sidecars and Architecture
Dapr achieves this by being divided into two distinct parts. The first part is the Dapr runtime, which is the core of Dapr, responsible for orchestration and acting as a portable control plane. The second part is the sidecar, which is unique to each Dapr-enabled application. A sidecar is a secondary process running alongside the application, serving as a transparent companion. When an application needs to use a building block, it communicates through the sidecar, which facilitates the use of the specified component. Integrating with a sidecar is straightforward, as all building blocks are exposed as HTTP or gRPC-based APIs. Applications simply call their own sidecar, which provides access to the necessary APIs to utilize the building blocks.
Buildings Blocks
As of version 1.1, Dapr provides multiple building blocks, with different maturities:
Source: https://docs.dapr.io/images/building_blocks.png
- Service Invocation: Service invocation allows applications to interact through well-defined endpoints using HTTP or gRPC messages. Dapr offers an endpoint that functions as a reverse proxy with integrated service discovery, incorporating distributed tracing and error handling.
- State Management: Application state refers to any data an application needs to retain beyond a single session. Dapr offers key/value-based state and query APIs with pluggable state stores for persistence.
- Pub/Sub Messaging: Pub/Sub is a loosely coupled messaging pattern in which publishers send messages to a topic, and subscribers subscribe to that topic to receive the messages. Dapr facilitates the pub/sub pattern for communication between applications.
- Workflows: The Workflow API allows you to define long-running, persistent processes or data flows that involve multiple microservices using Dapr workflows or workflow components. It can be integrated with other Dapr API building blocks, enabling workflows to invoke other services or retrieve secrets, thus offering flexibility and portability.
- Resource Bindings: A binding provides a two-way connection to an external cloud or on-premise service or system. Dapr enables you to invoke the external service through its binding API and allows your application to be triggered by events from the connected service.
- Actors: An actor is a self-contained unit of computation and state with single-threaded execution. Dapr offers an actor implementation based on the virtual actor pattern, providing a single-threaded programming model and automatically garbage collecting actors when they are not in use.
- Secrets: Dapr offers a secrets building block API that integrates with various secret stores, including public cloud stores, local stores, and Kubernetes, to manage secrets. Services can use the secrets API to retrieve sensitive information, such as a database connection string.
- Configuration: The Configuration API allows you to retrieve and subscribe to application configuration items from supported configuration stores. This enables an application to access specific configuration information, such as during startup or when changes are made in the configuration store.
- Distributed Lock: The distributed lock API allows you to lock a resource, ensuring that multiple instances of an application can access the resource without conflicts and maintain consistency guarantees.
- Cryptography: The Cryptography API allows you to perform cryptographic operations, such as encrypting and decrypting messages, without exposing keys to your application.
Portability is key aka Dapr Components
The impressive aspect of these building blocks is their interchangeability. To use a building block, you first need to configure a component. For example, with Pub/Sub Messaging, Dapr supports a variety of message broker systems such as Redis, Kafka, RabbitMQ, and cloud-based solutions like AWS SNS/SQS or Azure Service Bus. To use RabbitMQ, you would simply configure a component like this:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: order-pub-sub
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: host
value: "amqp://localhost:5672"
scopes:
- orderprocessing
- checkout
Once Dapr has loaded this component, utilizing it becomes a breeze. For example in C#, it would be as simple as:
await client.PublishEventAsync(order-pub-sub, checkout, eventPayload, cancellationToken);
This makes Dapr-enabled applications highly portable across different environments, as the core application code does not change. If you switch from RabbitMQ to Redis, for example, all you need to do is reconfigure the component, and everything will continue to work seamlessly.
Added capabilities
Since the sidecars are a central component, not only in providing APIs for the building blocks but also within the actual traffic flow, they offer additional capabilities in terms of observability, security, and resiliency.
- Observability: Dapr provides distributed tracing, metrics, and logs, all of which can be exported to a receiver of your choice using OpenTelemetry.
- Resiliency: The sidecar manages timeouts, retries/back-offs, and circuit breaking, as well as health checks for both the application and the sidecar itself.
- Security: Connections between sidecars and the control plane are secured using mTLS. Additionally, for service invocation and pub/sub, you can implement access policies to control who can access what, providing fine-grained access control.
These enhanced capabilities make Dapr a powerful tool for managing the complexities of distributed applications.
Is Dapr a Service mesh?
Let's answer that question simply: No, Dapr is not a service mesh. Does it offer overlapping capabilities? Yes, definitely. So, how exactly does Dapr compare to the likes of Linkerd, Istio, or potentially Cilium?
First off, a service mesh focuses primarily on networking inside a cluster, providing capabilities to enhance everything network-related. Dapr, on the other hand, is application-centric. It provides building blocks that make building distributed applications easier and quicker. While both use sidecars, this is a technical implementation detail rather than a defining feature of a service mesh. The overlapping capabilities between service meshes and Dapr include service-to-service encryption (mTLS), service invocation, metrics and tracing, and resiliency features like retries..
Source: https://docs.dapr.io/images/service-mesh.png
Networking-wise, when a Dapr-enabled app calls another app, it uses the application's ID known to Dapr. Dapr then routes that request through the underlying network, which could potentially be a service mesh that handles the actual networking path.
This distinction explains why it's not a matter of choosing Dapr or a service mesh, but rather how Dapr and a service mesh can work together effectively.
Example
Alright, that's been a lot of theory so far. Let's dive into an actual example. Since Dapr is a portable runtime, all it takes to run a Dapr-enabled app is an environment, either self-hosted or within Kubernetes (K8s) with the Dapr runtime installed. For this example, we'll use a simple local Kubernetes cluster (Minikube, Kind, K3s, etc.) and the well-known microservice-demo from Google Cloud, which I've edited to utilize Dapr.
Let's create a quick Kind cluster for this example, though any other local Kubernetes environment will work as well:
kind create cluster -n dapr-demo
Once the cluster is installed, the only thing missing is the actual Dapr runtime. Fortunately, Dapr provides a convenient CLI that we'll use heavily for this demo. To get the runtime into our local cluster, use the following command:
dapr init --dev -k
⌛ Making the jump to hyperspace...
ℹ️ Note: To install Dapr using Helm, see here: https://docs.dapr.io/getting-started/install-dapr-kubernetes/#install-with-helm-advanced
ℹ️ Container images will be pulled from Docker Hub
✅ Deploying the Dapr control plane with latest version to your cluster...
✅ Deploying the Dapr dashboard with latest version to your cluster...
✅ Deploying the Dapr Redis with latest version to your cluster...
✅ Deploying the Dapr Zipkin with latest version to your cluster...
ℹ️ Applying "statestore" component to Kubernetes "default" namespace.
ℹ️ Applying "pubsub" component to Kubernetes "default" namespace.
ℹ️ Applying "appconfig" zipkin configuration to Kubernetes "default" namespace.
✅ Success! Dapr has been installed to namespace dapr-system. To verify, run `dapr status -k' in your terminal. To get started, go here: https://aka.ms/dapr-getting-started
This command will install the following components inside our cluster as part of the Dapr runtime, which we can quickly check with:
dapr status -k
NAME NAMESPACE HEALTHY STATUS REPLICAS VERSION AGE CREATED
dapr-operator dapr-system True Running 1 1.13.5 1d 2024-07-03 15:10.23
dapr-sidecar-injector dapr-system True Running 1 1.13.5 1d 2024-07-03 15:10.23
dapr-dashboard dapr-system True Running 1 0.14.0 1d 2024-07-03 15:10.25
dapr-sentry dapr-system True Running 1 1.13.5 1d 2024-07-03 15:10.23
dapr-placement-server dapr-system True Running 1 1.13.5 1d 2024-07-03 15:10.23
Not only does this command install the Dapr runtime, but it also deploys a default Zipkin installation as a tracing backend and a Redis instance for pub/sub and state store purposes.
For this pub/sub setup, the necessary components will be applied to Kubernetes by default with the --dev command:
dapr components -k
⚠ In future releases, this command will only query the "default" namespace by default. Please use the --namespace flag for a specific namespace, or the --all-namespaces (-A) flag for all namespaces.
NAMESPACE NAME TYPE VERSION SCOPES CREATED AGE
default pubsub pubsub.redis v1 2024-07-08 13:51.37 38s
default statestore state.redis v1 2024-07-08 13:51.36 39s
With the runtime installed and healthy, we can proceed to the next step. The Dapr CLI also has a run command, which can launch apps and sidecars directly through Dapr. Additionally, it has a multi-run capability to start multiple apps (and their sidecars) together, similar to Docker Compose, but Dapr handles the sidecar placement and wiring. In the example repo mentioned above, I've already set this up. Hence, a simple:
kubectl apply -f ./src/emailservice/components/subscription.yml
dapr run -k -f dapr.yaml
This will bring up the entire microservice demo. To see that everything has started, we can use the Dapr dashboard:
dapr dashboard -k
ℹ️ Dapr dashboard found in namespace: dapr-system
ℹ️ Dapr dashboard available at: http://localhost:8080
You can also port-forward the frontend service to access the demo app:
kubectl port-forward -n default svc/frontend 8081:80
Once inside the demo app, you can go through an end-to-end flow, from selecting a product to completing the checkout. After completing the checkout, by checking the email service logs, you should see a result like this:
emailservice {"timestamp": 1720179148.6441565, "severity": "INFO", "name": "emailservice-server", "message": "A request to send order confirmation email for order fcd1bdec-3ac1-11ef-b1a7-faf1d76d4272 to someone@example.com has been received.", "taskName": null}
How does this work
This works by configuring the frontend service to use its sidecar for interservice communication by setting the addresses of those services to the sidecar.
Example:
env:
PRODUCT_CATALOG_SERVICE_ADDR: localhost:50001
AD_SERVICE_ADDR: localhost:50001
CART_SERVICE_ADDR: localhost:50001
CHECKOUT_SERVICE_ADDR: localhost:50001
CURRENCY_SERVICE_ADDR: localhost:50001
SHIPPING_SERVICE_ADDR: localhost:50001
RECOMMENDATION_SERVICE_ADDR: localhost:50001
SHOPPING_ASSISTANT_SERVICE_ADDR: localhost:50001
Now, the frontend service goes through the sidecar rather than directly to the service, allowing for control of the traffic flow. For pub/sub, the checkout service sends a gRPC request to the sidecar to emit the "checkout" event. The sidecar connects to Redis to actually emit the event. The email service's sidecar subscribes to the topic in Redis and, once an event is emitted, wraps the event into an HTTP call to notify the email service. Additionally, in the default Kubernetes configuration, all services communicate using mTLS, ensuring secure communication.
Conclusion and next steps
In conclusion, Dapr stands out as a robust and versatile tool for building cloud-native, distributed applications. Its architecture, based on the separation of the runtime and sidecar, enables seamless integration and flexibility across various programming languages and environments. The array of building blocks, such as Service Invocation, State Management, Pub/Sub Messaging, and many others, allows developers to easily incorporate essential functionalities into their applications.
Dapr's portability is a key advantage, allowing applications to remain consistent across different environments by simply reconfiguring components. This makes it highly adaptable and efficient, particularly in hybrid cloud setups or during migrations. The added features of observability, security, and resiliency further enhance its appeal, providing comprehensive support for modern application needs. Overall, Dapr is a valuable addition to any cloud-native toolkit, offering a comprehensive and flexible solution for managing the complexities of distributed application development and deployment.
More articles
fromManuel Zapf
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Manuel Zapf
Solution Architect
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.