How services work in Dapr

In getting started with .net core microservices with dapr we started to scratch the tip of the iceberg of what Dapr is and took a high level look at some of the tools and functionality that Dapr provides. We also created our first simple service using Dapr, just to introduce Dapr.

In this post, we’re going to scratch a little deeper and learn more about this distributed application runtime and how it can be used to quickly develop microservices.

A key aspect required in microservice architectures to provide a mechanism that enables services to discover and invoke each other to achieve tasks. In order to do this we first need to learn how to implement services and invoke them from other applications. These services can either be aware of Dapr, because they rely on its SDK or unaware of it as they simply invoke the service over a local HTTP endpoint.

How services work in Dapr

Services are at the core of Dapr, and it provides microservices with the ability to interact directly via service to service invocation. A developer making use of the Dapr to develop a service can easily make an API discoverable and reachable to other components inside the hosting environment, regardless of it is in a self-hosted or a Kubernetes cluster.

The Dapr service invocation API, is available as an abstraction offered by the Dapr .NET SDK and it provides :

  • Discoverability
  • Retry logic
  • Reliable Communication (HTTP & gRPC)

The diagram below is an attempt to visualise the paths of Requests and Responses taken to reach services traversing the Dapr time running in a Sidecar Container injected automatically by Dapr into each of the application pods.

Dapr Runtime
  • Service A makes a call intended for Service B, via the Dapr runtime on the local sidecar which discovers the location of Service B
  • The runtime on the local sidecar forwards the request to the Dapr local sidecar of Service B.
  • This Dapr sidecar invokes Service B on the configured application port
  • The sidecar receives back the result from the application logic
  • The result is in turn returned by Service B Dapr sidecar to Service A Dapr sidecar
  • The result of the request to Service B is returned to Service A.

Information

In future articles I will detail more about how this will all work in a practical implementation.

In an effort to distil this down to easily comprehend-able information I will stick to the simplified example we started to create in Getting started with .net core microservices with dapr

Even in this simple example Dapr can inject some useful cross-cutting behaviours for us:

  • Automatically retry calls upon failure
  • Make calls between services secure with mutual (mTLS) authentication, including automatic certificate rollover.
  • Control what operations clients can do using access control policies.
  • Capture traces and metrics for all calls between services to provide insights and diagnostics.
Learning Dapr
Building Distributed Cloud Native Applications

Get the authoritative guide to Dapr, the distributed application runtime that works with new and existing programming languages alike. Written by the model’s creators, this introduction shows you how Dapr not only unifies stateless, stateful, and actor programming models but also runs everywhere—in the cloud or on the edge.

Haishi Bai & Yaron Schneider

Buy Now Read Review

How Name Resolution works in Dapr

Dapr relies on the Kubernetes name resolution in the Hosting mode and mDNS in the Self-Hosted mode.  The mDNS protocol is meant to resolve host names to IP addresses within small networks that do not include a local name server. The mDNS service can be contacted using UDP queries over port 5353.

Service discovery is the component that enables any request to a Dapr sidecar to identify the corresponding sidecar and to reach the intended service endpoint.

Each Dapr Sidecar is identified by a string ID. The task of the name resolution is map the Dapr ID to a rout-able address.

Requests and Responses

Dapr forwards all request headers as well as query parameteres for HTTP requests and all metadata associated with gRPC requests. Although services can talk to Dapr through either HTTP or gRPC, Dapr sidecars always communicate with each other through gRPC.

Dapr supports common HTTP verbs including GET, POST, DELETE and PUT

Service Invocation with dapr .NET SDK

Dapr addresses these challenges by providing a service invocation API that acts as a combination of a reverse proxy with built-in service discovery, while leveraging built-in distributed tracing, metrics, error handling, encryption and more.

Dapr Docs

To invoke a service using Dapr, use the invoke API on any Dapr instance. The sidecar programming model encourages each of the applications to talk to its own instance of Dapr, which enables each dpar instance to discover and communicate with one another.

In Getting started with .net core microservices with dapr we took a basic web api project template and simply converted it into a Dapr app, we then simply invoked the service making use of dapr invoke using the terminal window. We will now build on this example to dive a little deeper and illustrate we can do exactly the same by developing a simple console application and use the HttpClient to invoke our dapr instance.

Lets first create our Simple Console app using

Shell

We’ll now simply modify the Program.cs with the following code

 class Program
    {
        static async Task Main(string[] args)
        {
            var client = new HttpClient();
            var forecasts =
                await client.GetFromJsonAsync<List<WeatherForecast>>(
                    "http://localhost:3500/v1.0/invoke/weatherservice/method/weatherforecast");
            
            foreach (var forecast in forecasts)
            {
                Console.WriteLine(
                    $"Date:{forecast.Date}, TemperatureC:{forecast.TemperatureC}, Summary:{forecast.Summary}");
            }
        }
    }
    internal class WeatherForecast
    {
        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public string Summary { get; set; }
    }

Dapr instance should already be running

This post assumes that you have the dapr instance from the getting started with dapr tutorial running.

When we run our application in your IDE, in my case I use Jetbrains Rider , we will see the result printed out in our terminal window.

Our little apps is working and we have successfully called our Dapr instance using HTTP. This might be great approach if you don’t want to depend on any third party libraries, but it can be error prone, and you have to include that long URL string in your application somewhere and what if you want to do more complex calls to your dapr instances?

Well now is a great time to introduce the Dapr client .NET SDK

What is the Dapr client .NET SDK

The Dapr client package allows you to interact with other Dapr applications from a .NET application, enable you to interface with all the Dapr Building blocks

Using the Dapr HttpClient

We’ll now extend our little console app to make use of the Dapr Client SDK, but first we need to install the SDK so using Nuget lets add the package

Shell

Once the package has completed its installation, we can slightly refactor our Main method as follows

  static async Task Main(string[] args)
        {
            var client = DaprClient.CreateInvokeHttpClient();
            var forecasts =
                await client.GetFromJsonAsync<List<WeatherForecast>>(
                    "http://weatherservice/weatherforecast");
            
            foreach (var forecast in forecasts)
            {
                Console.WriteLine(
                    $"Date:{forecast.Date}, TemperatureC:{forecast.TemperatureC}, Summary:{forecast.Summary}");
            }
        }

When we run our application, we will get exactly the same result. However, what matters here is the how. Even in this simple example you can tell that Dapr has does a bit of work for us in the background. Notice the simplified URL string we now use? We don’t need to include any port numbers etc. This is where the Dapr Name resolution has come into play, and provided an easy to remember and use URL string to call our services.

Despite this there are still some disadvantages to this approach the fact that we still have to include the URL in our application.

Dapr sidecar with DaprClient

We can make use of the DaprClientBuilder() to create the client. The settings for each DaprClient object are separate and cannot be changed after calling .Build().

The DaprClientBuilder contains settings for:

  • The HTTP endpoint of the Dapr sidecar
  • The gRPC endpoint of the Dapr sidecar
  • The JsonSerializerOptions object used to configure JSON serialization
  • The GrpcChannelOptions object used to configure gRPC
  • The API Token used to authenticate requests to the sidecar
    class Program
    {
        static async Task Main(string[] args)
        {
            var client = new DaprClientBuilder().Build();
            var forecasts =
                await client.InvokeMethodAsync<List<WeatherForecast>>(
                    HttpMethod.Get, "weatherservice", "weatherforecast");
            
            foreach (var forecast in forecasts)
            {
                Console.WriteLine(
                    $"Date:{forecast.Date}, TemperatureC:{forecast.TemperatureC}, Summary:{forecast.Summary}");
            }
        }
    }

Conclusion

We’ve dived a little deeper into using Dapr and seen how few different ways we can call a dapr service. We introduced the Dapr Client .NET SDK and seen how we can make use of it to call our very simple Dapr service.

    Like this content ?

    Sign up to my newsletter and I'll send you updates and more!