ASP.NET Certifications - Backend for Frontend pattern - Challenges and Pitfalls - Exams of ASP.NET - The message broker - What is a Modular Monolith?

Creating typed HTTP clients using Refit – Introduction to Microservices Architecture-1

The BFF service must communicate to the Baskets and Products services. The services are REST APIs, so we must leverage HTTP. We could leverage the out-of-the-box ASP.NET Core HttpClient class and IHttpClientFactory interface, then send raw HTTP requests to the downstream APIs. On the other hand, we could also create a typed client, which translates the HTTP calls to simple method calls with evocative names. We are exploring the second option, encapsulating the HTTP calls inside the typed clients.The concept is simple: we create one interface per service and translate its operation into methods. Each interface revolves around a service. Optionally, we can aggregate the services under a master interface to inject the aggregate service and have access to all child services. Moreover, this central access point allows us to reduce the number of injected services to one and improve discoverability with IntelliSense. Here’s a diagram representing this concept:

 Figure 19.25: UML class diagram representing a generic typed client class hierarchy.Figure 19.25: UML class diagram representing a generic typed client class hierarchy. 

In the preceding diagram, the IClient interface is composed and exposes the other typed clients, each of which queries a specific downstream API.In our case, we have two downstream services, so our interface hierarchy looks like the following:

 Figure 19.26: UML class diagram representing the BFF downstream typed client class hierarchy.Figure 19.26: UML class diagram representing the BFF downstream typed client class hierarchy. 

After implementing this, we can query the downstream APIs from our code without worrying about their data contract because our client is strongly typed.We leverage Refit, an open-source library, to implement the interfaces automatically.

We could use any other library or barebone ASP.NET Core HttpClient; it does not matter. I picked Refit to leverage its code generator, save myself the trouble of writing the boilerplate code, and save you the time of reading through such code. Refit on GitHub: https://adpg.link/hneJ.

I used the out-of-the-box IHttpClientFactory functionalities in the past, so if you want to reduce the number of dependencies in your project, you can also use that instead. Here’s a link to help you get started: https://adpg.link/HCj7.

Refit acts like Mapperly and generates code based on attributes, so all we have to do is define our methods, and Refit writes the code.

The BFF project references the Products and Baskets projects to reuse their DTOs. I could have architected this in many different ways, including hosting the typed client in a library of its own so we could share it between many projects. We could also extract the DTOs from the web applications to one or more shared projects so we don’t depend on the web applications themselves. For this demo, there is no need to overengineer the solution.

Let’s look at the typed client interfaces, starting with the IBasketsClient interface:

using Refit;
using Web.Features;
namespace REPR.BFF;
public interface IBasketsClient
{
    [Get(“/baskets/{query.CustomerId}”)]
    Task<IEnumerable<Baskets.FetchItems.Item>> FetchCustomerBasketAsync(
        Baskets.FetchItems.Query query,
        CancellationToken cancellationToken);
    [Post(“/baskets”)]
    Task<Baskets.AddItem.Response> AddProductToCart(
        Baskets.AddItem.Command command,
        CancellationToken cancellationToken);
    [Delete(“/baskets/{command.CustomerId}/{command.ProductId}”)]
    Task<Baskets.RemoveItem.Response> RemoveProductFromCart(
        Baskets.RemoveItem.Command command,
        CancellationToken cancellationToken);
    [Put(“/baskets”)]
    Task<Baskets.UpdateQuantity.Response> UpdateProductQuantity(
        Baskets.UpdateQuantity.Command command,
        CancellationToken cancellationToken);
}

The preceding interface leverages Refit’s attributes (highlighted) to explain to its code generator what to write. The operations themselves are self-explanatory and carry the features’ DTOs over HTTP.Next, we look at the IProductsClient interface:

using Refit;
using Web.Features;
namespace REPR.BFF;
public interface IProductsClient
{
    [Get(“/products/{query.ProductId}”)]
    Task<Products.FetchOne.Response> FetchProductAsync(
        Products.FetchOne.Query query,
        CancellationToken cancellationToken);
    [Get(“/products”)]
    Task<Products.FetchAll.Response> FetchProductsAsync(
        CancellationToken cancellationToken);
}

The preceding interface is similar to IBasketsClient but creates a typed bridge on the Products API.

The generated code contains much gibberish code and would be very hard to clean enough to make it relevant to study, so let’s assume those interfaces have working implementations instead.

Next, let’s look at our aggregate:

public interface IWebClient
{
    IBasketsClient Baskets { get; }
    IProductsClient Catalog { get; }
}

Leave a Reply

Your email address will not be published. Required fields are marked *