- When implementing communication across microservices, developers have to deal with nuances of multiple programming languages and runtimes.
- Dapr provides building blocks with pre-built integration with several state stores, secret stores, and messaging brokers. This eases microservices development for developers and brings agility to delivery.
- Dapr also helps adopt standards and industry best practices into the technology stack.
- With an API-based integration mechanism, Dapr can be used as a core layer for microservices implemented using any technology, runtime or framework.
- Visual Studio Code, aided by extensions, provides great support for developing Docker files, building container images, publishing them to container repositories, and deploying workload on Kubernetes clusters like AKS.
Microservices is an architectural pattern that enables teams to work independently and in parallel, on delivering capabilities that are functionally distinct. Reduced dependency among teams enables higher velocity in rolling out features, thereby leading to faster time-to-market.
Each team makes technology choices based on what suits them depending upon factors like fitment for the purpose, the team’s skill set, and several others. Thus, multiple technology stacks are fairly common in a microservices architecture. This brings challenges in intercommunication among services.
Since each microservice usually realizes one distinct feature, the overall number can easily grow in an enterprise application, thus compounding the possible combinations. This leads to what is commonly known as “Microservices DeathStar.”
Dapr is a useful tool to solve these challenges. It allows engineering organizations to gain productivity by training developers on a common set of tools and techniques. It also helps adopt standardization in development, deployment, and debugging.
What is Dapr?
Distributed Application Runtime (Dapr) is a CNCF project like Kubernetes. It is not just a framework or set of reusable libraries. It is rather a runtime that provides capabilities to solve many common concerns like service discovery, service-to-service communication, state management, configuration, secrets management, and others. Dapr exposes APIs for each of these capabilities which can be invoked from applications using HTTP or gRPC. With this approach, Dapr’s footprint in application code is limited to an API call, allowing Dapr to easily integrate with any language or runtime. The figure below depicts capabilities provided by Dapr, supported infrastructure platforms, and integration options using APIs.
Dapr building blocks
Capabilities provided by Dapr are called building blocks. Below is a short description of what each building block provides.
- Service-to-Service Invocation: Make API calls across services using constructs that are programming language/runtime agnostic, over a secure, mutually authenticated communication channel.
- State Management: Manage application state as simple key-value pairs without having a static coupling with the underlying state provider. This building block allows applications to switch between multiple state providers, like Redis or Azure SQL, by switching configuration and without code changes.
- Publish and Subscribe: Leverage this building block to implement messaging-based communication between microservices using any of the supported messaging systems like Azure Event Hubs, Kafka, Rabbit MQ, etc.
- Actors: Implement actor framework to support highly concurrent scenarios.
- Resource Bindings and Triggers: Trigger handlers in application code based on events raised by external systems and services or invoke external services from within your code by making a call to Dapr APIs and passing necessary parameters without explicitly implementing calls to external services e.g., invoke code based on an SMS sent via Twilio.
- Observability: Monitor service call graph and trace end-to-end traffic across the entire system.
- Secrets: Store secrets like API keys, database credentials, and certificate credentials in a secure provider like Azure Key Vault, HashiCorp Vault, etc.
- Configuration: Retrieve and subscribe to changes in application configuration items for supported configuration stores.
- Distributed Lock: Implement locking on shared resources used by multiple distributed consumers in a manner that guards against deadlocks.
The list of building blocks is growing and the number of supported providers for each building block is also increasing so it is useful to check the latest documentation for supported services.
Service mesh vs. Dapr
Service mesh is a popular technology used in microservices-style architectures. Service mesh provides several capabilities like secure communication, service discovery, reliability, traffic routing, observability, etc. Although some of the capabilities provided by Dapr overlap with those of service mesh, Dapr is more focused on making it easier to develop applications for a microservices-oriented architecture while Service mesh is inclined toward platform/operational aspects. Dapr abstracts the application’s integration of state stores, secret stores, and configuration providers irrespective of programming language or framework and enables developers to focus on building business logic.
How to host Dapr
Dapr can be self-hosted as a process running on an operating system or it can be configured to run as a sidecar on Kubernetes. This allows all kinds of applications, whether they are containerized or not, running on cloud or on-premise or on edge infrastructure, deployed on physical or virtual machines, to make use of its capabilities.
APIs exposed by Dapr can be consumed directly via HTTP/gRPC or language-specific SDKs can be used. More information about language-specific SDKs can be found here. Communication across microservices involves the below-mentioned general concerns, among others.
- Service discovery
- Secure transport
- Retry policies for transient failures
Dapr handles all these aspects without developers having to find solutions for each runtime. It discovers other services using the application id registered with it. Calls between services are secured with mutual TLS authentication with automatic certificate rollover and retries are made automatically by Dapr runtime upon failure.
Using Dapr on AKS to communicate between .NET and Python-based microservices
We’ll now look at leveraging Dapr for implementing communication between two microservices—one is OrderHistoryService, a RESTful service implemented using ASP.NET Web API and the other is OrderDetailsService developed using Python FastAPI framework. Both frameworks provide support for reactive programming and containerization, thus making an excellent choice for cloud-native microservices development. The services are packaged as containers using Docker and pushed to Azure Container Registry(ACR). Dapr is installed as a sidecar on the AKS cluster. OrderHistoryService uses Dapr to invoke the API provided by OrderDetailService to retrieve order information. The figure below presents an overview of how Dapr facilitates communication between these services. There is an overhead that comes with the introduction of Dapr. Although services communicate with sidecar containers locally within the same pod and Dapr uses gRPC to invoke calls among its sidecar instances, it is still advisable to carry out performance tests and understand the latency introduced with the usage of Dapr.
Visual Studio Code by way of extensions has very good support for developing services, authoring Docker files for containerization, integrating with Dapr, pushing container images to ACR, and deploying applications on a Kubernetes platform like Azure Kubernetes Platform (AKS). We’ll use VS Code extensions published by Microsoft for the above-mentioned purposes. Using these ensures the correctness of syntax and helps with the identification of dependencies that need to be bundled in respective container images. Let us now go through the stepwise procedure to deploy services as shown in above figure on AKS and communicate using Dapr.
Steps to build microservices and deploy on AKS cluster
- Configure Dapr and AKS cluster
- Create an AKS cluster and install Dapr there following instructions from here. After successful installation, dapr services should be running on AKS cluster as seen below.Dapr-dashboard is a UI that shows services running in the environment. As the name suggests, dapr-side-injector injects the dapr container as a sidecar in an AKS pod. Dapr-operator is responsible for managing bindings and configurations while dapr-sentry manages certificates used for mutual TLS.
- Execute the below command on Azure CLI to provide permissions to AKS cluster to pull images from ACR : az aks update -n > -g > –attach-acr >
- Create “OrderHistory” service with ASP.NET Web API using Visual Studio Code
- Open Visual Studio Code as an administrator and create a Web API project.
- Add “Dapr” using the Nuget package manager.
- In Get() action method of the controller class, implement invocation of “orderdetails” method on “OrderDetailService” passing orderId as a parameter. The service here makes a call to “invoke” API on the Dapr instance. It passes the HTTP verb, name of service to be called, and the method to be called with parameters.
- Use Docker extension for Visual Studio Code, published by Microsoft, to author a Docker file. This will automatically identify dependencies to be pushed into the container image, alongside application-specific libraries and executables.
- Build the container image and publish to Azure Container Registry (ACR) using the same VS Code extension. The extension allows connecting to the Azure account and working with repositories and container images.
- Add a “deployment.yml” file to the workspace. Kubernetes extension, published by Microsoft automatically generates required elements within “deployment.yml” to target deployment on Kubernetes cluster.
- To inject “Dapr” as a sidecar in the pod where “OrderHistoryService” container is deployed, add annotations to the “deployment.yml” file, as shown in the figure below.
- Add a “service.yml” file to “OrderHistoryService” to create a service with a public IP address.
- Create “OrderDetailService” using Python FastAPI framework
- Open Visual Studio Code as an administrator and create a Python service using FastAPI. The service implements a method “get_orderdetails” which accepts “order Id” and returns details of the corresponding order.
- Use the VS Code extensions, in the same way as mentioned earlier, to create a Docker file, push the container image to ACR and create a “deployment.yml”.
- Same annotations with respective values for “OrderDetailService” should be added to “deployment.yml” to inject the “Dapr” sidecar in the pod where the “OrderDetailService” container is deployed. This enables communication between sidecar containers across the pods.
Accessing OrderHistoryService and passing order id as a parameter in query string (http://>/orderhistory?orderid=>) brings up details of order from OrderDetailsService.
Viewing logs for application and Dapr containers
It is critical to be able to check logs generated by services for troubleshooting issues. Using a Microsoft-published VSCode extension for Kubernetes, console logs generated by each container can be easily viewed. The steps to view logs are as follows:
- If deployed successfully, the below pods will be running on AKS.
- Right-click on a pod and choose logs.
- This brings up options to choose a container from among “orderhistoryservice” and “daprd” and view corresponding logs.
- Logs for the “OrderHistory” container.
- Logs for the “dapr” sidecar container.
To be competitive, organizations need to keep tailoring their digital solutions to customers’ evolving needs. This is where the requirement arises for a software architecture that is easy to change over a period of time. Microservice built along domain boundaries, using principles of domain-driven design, promise to fulfill this need for architectural evolution. Dapr is a tool that allows software developers to put into practice the idea of easy evolution of software. In this article, we have seen how Dapr brings uniformity in communication across microservices, even if they are implemented using different technologies and frameworks. Dapr handles common concerns like service discovery, error handling, and secure communication thereby reducing the scope of errors and allowing developers to focus on building business functions. Other building blocks of Dapr enable easy integration and configuration-driven switching for state-stores, message brokers, etc. Therefore, Dapr is definitely a useful tool for building an enterprise-grade microservices-based software solution.