Communication between microservices using typed clients and Dapr — Part 2
In Part 1, we looked into how we can call across microservices using a strongly typed client generated by using Swagger and NSwag. In this post, we will look into using Dapr for service discovery and how this simplifies the process.
Dapr, the distributed application runtime, has many great features available out of the box. We will be using the service invocation building block to call our User API from the Registry API to validate the user.
The first step is to install the Dapr CLI. The instructions to install the cli can be found here. After installing the CLI, initalize it using the steps documented here. Ensure that the CLI is installed and intialized before proceeding to the next step.
We will use the same source code as before with the Registry and the User APIs which can be found here. Switch to the branch dapr-step (git checkout dapr-step) to view the source code.
The Registry API now has two new libraries installed,
Note: We will not be using the NSwag generated client from the previous blog post but will be using the Dapr client instead, to make the call to the user microservice.
The User API does not have the Dapr client SDKs installed since we dont need it. Instead of running the user API from visual studio, we will use the Dapr CLI to run it for us. In powershell or bash window, navigate to the User API project directory and run the following command,
dapr run --app-id UserApp --app-port 5001 --dapr-http-port 6001 -- dotnet run
So what is this command doing?
- The first argument to the dapr run command is the app id for this microservice which in this case is “UserApp”. This identifier will be used later when we need to call this API from powershell and from code.
- The second argument is the app port and that is set to 5001. This is the port where the actual .Net API is listening for requests.
- When we run this command, behind the scenes, a sidecar (process) is started which will proxy any calls to this API, and this argument specifies the HTTP port that the sidecar is listening on. The sidecar proxies the requests to the API.
- The last part of this command is the dotnet CLI command to run the API. On running the command, the text logged in blue shows the output from the dotnet run command.
To test the API, create a new powershell window, and run the following command to invoke the get user details endpoint,
Invoke-RestMethod -Uri 'http://localhost:6001/v1.0/invoke/UserApp/method/api/user/1'
To break down this URL,
- The base URL is set to call the sidecar running on port 6001 and NOT the actual API which is running on port 5001. The sidecar will proxy the request on our behalf.
- The second part of the URL is the version which is set to v1.0 by default.
- The third part is the key word invoke followed by the app id that we would like to invoke which in this case is the User API which was given the name UserApp. This was specified when we ran the dapr run command to run the User API (step 1 in the previous section).
- The next keyword is method followed by the relative URL of the endpoint that we need to call which in this case is api/user/{userId} (from the UserController).
Now that we know how to call this API from powershell, leave it running and lets see how to consume it from the Registry API to validate the user. The first step is to register the dapr client in the startup.cs file,
The AddDapr extension method adds the dapr client to the DI container which can now be injected into the controller. The registry controller looks like this,
The first line in the controller sets a variable named UserAppID set to “UserApp” which is the identifier of the user API that Dapr is currently running. The register endpoint is using the dapr client to invoke the get user details method using the line of code,
await _daprClient.InvokeMethodAsync<User>(HttpMethod.Get, UserAppId, $"/api/user/{request.BuyerUserId}", cancellationToken);
We can now break down this statement to see what its doing,
- We are invoking a method on the dapr client named InvokeMethodAsync with the first argument being the method type (get, post, put, patch etc).
- The second argument is the app id which is the identifier of the User API which we started and invoked in a previous step from a powershell window.
- The third argument is the relative URL for invoking the get user details endpoint in the User API.
- The User class has been duplicated from the User API but we can easily generate this with NSwag like we did when we were using the client from the previous blog post.
We can now test the registry endpoints to make sure that the internal call to the user API is going through. To run the registry API, run the following command,
dapr run --app-id RegistryApp --app-port 5002 --dapr-http-port 6002 -- dotnet run
To test the Registry API, create an new powershell window, and run the following command to invoke the register endpoint which internally calls the user endpoint. The response should be a 404 since the user id is invalid.
Invoke-RestMethod -Method Put -ContentType 'application/json' -Body '{"registryId": 1,"productId": 1,"buyerUserId": 8}' -Uri 'http://localhost:6002/v1.0/invoke/RegistryApp/method/api/registry/register'
Now lets call with a valid request and ensure that it is saved correctly,
Invoke-RestMethod -Method Put -ContentType 'application/json' -Body '{"registryId": 1,"productId": 1,"buyerUserId": 4}' -Uri 'http://localhost:6002/v1.0/invoke/RegistryApp/method/api/registry/register'
Verify by calling the get method,
Invoke-RestMethod -Uri 'http://localhost:6002/v1.0/invoke/RegistryApp/method/api/registry/1'
If we need to debug at anytime, we can still attach to the process that dotnet is running from visual studio,
To run this on kubernetes, Dapr needs to be installed on the cluster and the app id for each application is specified during deployment. The yaml to do this is incuded in the solution in the folder named “Kube” for each microservice,
The sidecar is injected during pod deployment, so by just configuring the app-id, we are able to find and invoke services and methods without much effort.
In addition to this, instead of duplicating the User class in the Registry API, we can still use the class created by NSwag. Although we wouldnt be using the client, we can still leverage the classes to serialize and deserialize the response giving us the best of both worlds.
This completes the series around communciation between microservices. We’ve seen how to call another service using a typed client using swagger and NSwag in the first part, and then using the service invocation building block with Dapr.
If you have further questions on the topic, feedback or just want to say hi you can hit me up on twitter or linkedin.
Thank you Alex Shields for your valuable feedback on these posts!