DEV Community

Thang Chung
Thang Chung

Posted on Edited on

How to make Dapr client works well with Refit and RestEase in 5 minutes

Prerequisition

If you want to go directly to the source code, then it is at https://github.com/thangchung/practical-clean-ddd ⭐⭐⭐⭐⭐

Begin story

With the Dapr client in code, we need to follow its patterns and the way to call another Daprized App to its Rest API. Normally, we use service invocation to the Dapr server app. The code as below

var client = DaprClient.CreateInvokeHttpClient(appId: "routing");
var deposit = new Transaction  { Id = "17", Amount = 99m };
var response = await client.PostAsJsonAsync("/deposit", deposit, cancellationToken);
var account = await response.Content.ReadFromJsonAsync<Account>(cancellationToken: cancellationToken);
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

And this way of calling is really easy to make Leaky Abstraction
But what we think in my mind is something like this

public interface IRoutingApi
{
    [Post("/deposits")]
    Task<Account> DepositTransaction([Body] Transaction body);
}

var account = RestService.For<IRoutingApi>("http://routing");
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

We can use Refit or RestEase to make it better to read and use just like above.

It must be easy and in a Rest-way to call the Rest API, right? Make Rest back to its natural power.

With Dapr.AspnetCore 1.0 we can luckily make Dapr client work naturally with Refit or RestEase.

Ryan Nowak had worked on the task to make it work together really well 👍. You can find the version of Dapr client with Refit at https://github.com/rynowak/dapr-httpclient-extravaganza/blob/main/samples/BankClient/RefitExample.cs, and the official implementation of the adaptor for Dapr client can be found at https://github.com/dapr/dotnet-sdk/blob/master/src/Dapr.Client/InvocationHandler.cs

Dapr loves RestEase as well âĪïļâĪïļâĪïļ

In this article, we focus on how to make Dapr client work with RestEase. Let imagine that we have a service with the name customer service which lets end-user register their information. And we should have the Rest API like POST http://localhost:5000/customer-api/customers, then in this API we call to another service, let says setting service to check whether the country of customer is valid or not? We have the Rest API for setting service just like GET http://settingapp:5005/api/countries/{id}, the code for this API as below

[HttpGet("/api/countries/{id:guid}")]
public override async Task<ActionResult<CountryDto>> HandleAsync(Guid id,
    CancellationToken cancellationToken = new())
{
    var request = new Query {Id = id};

    return Ok(await Mediator.Send(request, cancellationToken));
}
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Now we set up the code to call this API in customer service as below

Define the API contract:

// ICountryApi.cs
public interface ICountryApi
{
    [Get("api/countries/{countryId}")]
    Task<ResultModel<CountryDto>> GetCountryByIdAsync([Path] Guid countryId);
}
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Wire up some code in the Startup.cs

// Startup.cs
var settingAppUri = IsRunOnTye
    ? $"http://{AppConsts.SettingAppName}:5005"
    : "http://localhost:5005"; 

services.AddScoped<InvocationHandler>();
services.AddRestEaseClient<ICountryApi>(settingAppUri, client =>
{
    client.RequestPathParamSerializer = new StringEnumRequestPathParamSerializer();
}).AddHttpMessageHandler<InvocationHandler>();
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

And the code in the handler:

// CreateCustomer.cs
internal class Handler : IRequestHandler<Command, ResultModel<CustomerDto>>
{
    private readonly IRepository<Customer> _customerRepository;
    private readonly ICountryApi _countryApi;

    public Handler(IRepository<Customer> customerRepository,
        ICountryApi countryApi)
    {
        _customerRepository = customerRepository ?? throw new ArgumentNullException(nameof(customerRepository));
        _countryApi = countryApi ?? throw new ArgumentNullException(nameof(countryApi));
    }

    public async Task<ResultModel<CustomerDto>> Handle(Command request,
        CancellationToken cancellationToken)
    {
        var alreadyRegisteredSpec = new CustomerAlreadyRegisteredSpec(request.Model.Email);

        var existingCustomer = await _customerRepository.FindOneAsync(alreadyRegisteredSpec);

        if (existingCustomer != null)
            throw new Exception("Customer with this email already exists");

        // check country is exists and valid
        var (countryDto, isError, _) = await _countryApi.GetCountryByIdAsync(request.Model.CountryId);
        if (isError || countryDto.Id.Equals(Guid.Empty))
        {
            throw new Exception("Country Id is not valid.");
        }

        var customer = Customer.Create(request.Model.FirstName, request.Model.LastName, request.Model.Email, request.Model.CountryId);

        var created = await _customerRepository.AddAsync(customer);

        return new ResultModel<CustomerDto>(new CustomerDto
        {
            Id = created.Id,
            FirstName = created.FirstName,
            LastName = created.LastName,
            Email = created.Email,
            CountryId = created.CountryId
        });
    }
}
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

The main codes to notice as

// check country is exists and valid
var (countryDto, isError, _) = await _countryApi.GetCountryByIdAsync(request.Model.CountryId);
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Now let use tye to init the core components:

$ tye run
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Go to http://localhost:8000, and make sure all the apps running well.

Finally, you make a call to POST http://localhost:5000/customer-api/customers with the body as

{
  "model": {
    "firstName": "firstName 1",
    "lastName": "lastName 1",
    "email": "email1@nomail.com",
    "countryId": "18a4a8ae-3338-484a-a4ed-6e64d13d84dc"
  }
}
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Now you can see the logs as below:

[customerapp_5ed4a92a-f]: info: System.Net.Http.HttpClient.CoolStore.AppContracts.RestApi.ICountryApi.LogicalHandler[100]
[customerapp_5ed4a92a-f]: Start processing HTTP request GET http://settingapp:5005/api/countries/18a4a8ae-3338-484a-a4ed-6e64d13d84dc
[customerapp_5ed4a92a-f]: info: System.Net.Http.HttpClient.CoolStore.AppContracts.RestApi.ICountryApi.ClientHandler[100]
[customerapp_5ed4a92a-f]: Sending HTTP request GET http://127.0.0.1:50910/v1.0/invoke/settingapp/method/api/countries/18a4a8ae-3338-484a-a4ed-6e64d13d84dc
[customerapp_5ed4a92a-f]: info: System.Net.Http.HttpClient.CoolStore.AppContracts.RestApi.ICountryApi.ClientHandler[101]
[customerapp_5ed4a92a-f]: Received HTTP response headers after 695.0478ms - 200
[customerapp_5ed4a92a-f]: info: System.Net.Http.HttpClient.CoolStore.AppContracts.RestApi.ICountryApi.LogicalHandler[101]
[customerapp_5ed4a92a-f]: End processing HTTP request after 710.2258ms - 200
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Have you noticed some lines of logs above at

...
Start processing HTTP request GET http://settingapp:5005/api/countries/18a4a8ae-3338-484a-a4ed-6e64d13d84dc
...
Sending HTTP request GET http://127.0.0.1:50910/v1.0/invoke/settingapp/method/api/countries/18a4a8ae-3338-484a-a4ed-6e64d13d84dc
...
🏆 āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ” ðŸ† āļĒāļ­āļ”āļ™āļīāļĒāļĄ: thangchung/how to make dapr client works well with refit and restease m - āļŠāļĢāļļāļ›āļ”āļĢāļēāļĄāđˆāļēāļĨāđˆāļēāļŠāļļāļ”

Here ya go! Dapr client helps us to handle the work which is mainly on RestEase way.

Easy peasy, right? Give the star ⭐ for the repository below if you feel like this article helps you out. Thank you! âĪïļâĪïļâĪïļ

Source code of this article can be found at https://github.com/thangchung/practical-clean-ddd ⭐⭐⭐⭐⭐

Top comments (0)