Olá! Bem-vindo de volta.
Na lição anterior, desvendamos como o .NET Aspire resolve o problema fundamental de "onde está o serviço?" por meio do seu mecanismo de descoberta de serviços. Vimos que, ao usar WithReference() no AppHost, podemos nos comunicar entre serviços usando nomes lógicos (como http://products-api), eliminando a necessidade de URLs fixas e frágeis.
Hoje, vamos dar o próximo passo lógico. Agora que sabemos endereçar os serviços, qual é a melhor maneira de escrever o código que realiza essa comunicação? Esta lição mergulha em uma das melhores práticas do desenvolvimento .NET moderno: os Typed HTTP Clients. Nosso objetivo de aprendizagem é configurar e injetar HttpClients tipados para comunicação entre serviços, usando os padrões de referência de serviço do Aspire. Ao final, você saberá como construir um código de comunicação entre serviços que não é apenas funcional, mas também robusto, manutenível e fácil de testar.
Por que a Simples Instanciação de HttpClient é Problemática?
Antes de entrarmos na solução, é crucial entender o problema que ela resolve. Sua experiência como desenvolvedor back-end pode já tê-lo exposto aos desafios do uso incorreto do HttpClient. A abordagem ingênua seria:
// Abordagem problemática
using var client = new HttpClient();
var result = await client.GetStringAsync("http://service-b/data");
Embora pareça inofensivo, este padrão, especialmente dentro de um loop ou em um endpoint de API muito requisitado, pode levar a dois problemas clássicos:
- Esgotamento de Sockets (
Socket Exhaustion): Cadanew HttpClient()pode reservar um novo socket. Mesmo com o blocousing, o sistema operacional leva um tempo para liberar esse socket. Sob carga pesada, você pode esgotar o pool de sockets disponíveis, resultando em exceçõesSocketException. - Não Respeitar Alterações de DNS: A alternativa de usar uma instância estática ou singleton de
HttpClient(static HttpClient client = new HttpClient();) resolve o esgotamento de sockets, mas cria outro problema. Essa instância única armazena em cache as informações de DNS indefinidamente e não detectará alterações se, por exemplo, um serviço se mover para um novo endereço IP.
Para entender a fundo esses problemas, a documentação da Microsoft oferece uma excelente explicação.
Use IHttpClientFactory to implement resilient HTTP requests
Este documento da Microsoft detalha os problemas históricos com a classe HttpClient e introduz a solução moderna, IHttpClientFactory.
Leia a seção "Issues with the original HttpClient class available in .NET". O objetivo é solidificar o seu entendimento sobre o 'porquê' de precisarmos de um padrão melhor do que simplesmente instanciar HttpClient diretamente.
A Solução .NET: IHttpClientFactory e Typed Clients
Para resolver esses dilemas, o .NET introduziu a IHttpClientFactory. Esta fábrica gerencia o ciclo de vida dos HttpMessageHandlers (os objetos que realmente lidam com as conexões HTTP), agrupando-os e reciclando-os de forma inteligente. Isso resolve ambos os problemas: evita o esgotamento de sockets ao reutilizar handlers e respeita as alterações de DNS ao reciclá-los periodicamente.
Existem várias maneiras de usar a IHttpClientFactory, mas a mais estruturada e recomendada para comunicação entre serviços é o padrão Typed Client.
Um Typed Client é uma classe que encapsula toda a lógica de comunicação com uma API remota específica. Em vez de espalhar HttpClient.GetAsync(...) por todo o seu código, você centraliza tudo em um só lugar.
Benefícios de um Typed Client:
- Encapsulamento: A lógica para construir URLs, serializar/desserializar objetos e lidar com headers fica contida em uma única classe.
- Forte Tipagem: Os métodos retornam objetos fortemente tipados (ex:
Task<Product>) em vez deHttpResponseMessage, tornando o código do consumidor mais limpo. - Configuração Centralizada: Você pode configurar o
BaseAddress, timeouts e políticas de resiliência (que veremos mais tarde) em um único local (Program.cs). - Testabilidade: É muito mais fácil de mocar (
mock) uma interface comoIProductsApiClientem seus testes unitários do que mocar umHttpClient.
Veja a aparência de um Typed Client:

O documento da Microsoft que você acabou de ler também descreve este padrão em detalhes.
Use IHttpClientFactory to implement resilient HTTP requests
Vamos continuar na documentação da Microsoft para ver como o padrão Typed Client é implementado.
Agora, leia as seções "How to use Typed Clients with IHttpClientFactory" e "Implement your Typed Client classes that use the injected and configured HttpClient". Preste atenção em como o Typed Client é uma classe normal que recebe um HttpClient no construtor e como ela é registrada no contêiner de injeção de dependência.
Unindo Tudo: Typed Clients com Service Discovery do Aspire
Agora, vamos conectar o padrão Typed Client com o que aprendemos na lição anterior sobre o service discovery do Aspire. A "mágica" acontece no momento em que registramos nosso Typed Client no Program.cs do serviço consumidor.
Considere um AppHost com um serviço products-api e um store-frontend que o consome:
// No projeto AppHost
var productsApi = builder.AddProject<Projects.ProductsApi>("products-api");
var storeFrontend = builder.AddProject<Projects.StoreFrontend>("store-frontend")
.WithReference(productsApi);
No projeto store-frontend, criamos nossa classe ProductsApiClient. Em seguida, no Program.cs do store-frontend, nós a registramos da seguinte forma:
// No Program.cs do store-frontend
// 1. Adicionamos o nosso Typed Client ao contêiner de DI.
builder.Services.AddHttpClient<ProductsApiClient>(client =>
{
// 2. Aqui está a conexão! Usamos o nome lógico do serviço
// definido no AppHost como o BaseAddress.
client.BaseAddress = new Uri("https+http://products-api");
});
É isso! Ao fazer isso, você está instruindo a IHttpClientFactory a:
- Criar instâncias de
HttpClientpara o seuProductsApiClient. - Configurar cada instância com um endereço base que será resolvido dinamicamente pelo mecanismo de service discovery do Aspire.
O vídeo a seguir demonstra de forma prática a evolução de uma abordagem menos estruturada para um Typed Client e, finalmente, sua integração com o .NET Aspire para eliminar completamente as URLs fixas.
5 HttpClient Best Practices Every .NET Dev Should Know
Este vídeo de Milan Jovanović sobre as melhores práticas do HttpClient é perfeito para o nosso tópico. Ele o guiará na refatoração de um código para usar um Typed Client e, em seguida, conectá-lo ao service discovery do Aspire.
Assista em duas partes: Primeiro, assista de 00:04:00 a 00:08:13. Observe como ele cria a classe GitHubClient para encapsular a lógica HTTP. Isso é a criação do Typed Client. Em seguida, assista de 00:09:59 a 00:13:28. Preste atenção em como ele modifica a configuração no AppHost e no registro do HttpClient para usar um nome de serviço lógico em vez de uma URL hard-coded. Esta é a integração com o Aspire.
O fluxo completo, unindo a lição anterior e esta, é o seguinte:

Teste seu entendimento!
Você tem um projeto AppHost que define dois serviços: users-api e notifications-api. O notifications-api precisa chamar o users-api.
AppHost Program.cs:
var usersApi = builder.AddProject<Projects.UsersApi>("users-api");
builder.AddProject<Projects.NotificationsApi>("notifications-api")
.WithReference(usersApi);
Você criou um Typed Client UsersApiClient no projeto NotificationsApi. Qual trecho de código você deve adicionar ao Program.cs do NotificationsApi para registrar corretamente o UsersApiClient e conectá-lo ao service discovery do Aspire?
a) builder.Services.AddSingleton<UsersApiClient>();
b) builder.Services.AddHttpClient<UsersApiClient>(c => c.BaseAddress = new Uri(builder.Configuration["Services:UsersApi:Http:0"]));
c) builder.Services.AddHttpClient<UsersApiClient>(c => c.BaseAddress = new Uri("https+http://users-api"));
d) builder.Services.AddScoped<UsersApiClient>(sp => new UsersApiClient(new HttpClient()));
Show answer
A resposta correta é a (c). Este é o padrão exato que aprendemos:
AddHttpClient<UsersApiClient>registra o Typed Client e configura a injeção doHttpClient.c.BaseAddress = new Uri("https+http://users-api")usa o nome lógico do serviço (users-api) definido no AppHost. O Aspire e aIHttpClientFactorycuidarão da resolução do endereço real em tempo de execução.
A opção (b) é incorreta porque tenta ler a variável de ambiente diretamente, o que é um detalhe de implementação que o padrão de service discovery abstrai para nós.
Conclusão
Nesta lição, você aprendeu a implementar a comunicação entre serviços de uma maneira robusta e padronizada no ecossistema .NET e Aspire. Ao combinar o padrão Typed Client com o service discovery do Aspire, ganhamos o melhor dos dois mundos: um código cliente limpo, encapsulado e testável, que se comunica de forma transparente com serviços cujos endereços são gerenciados dinamicamente.
Principais Pontos:
- Evite
new HttpClient()para prevenir problemas de esgotamento de sockets e cache de DNS. - Use o padrão Typed Client para encapsular a lógica de comunicação com APIs externas, tornando seu código mais limpo e fácil de manter.
- Registre seus Typed Clients usando
builder.Services.AddHttpClient<TClient>(). - Para integrar com o Aspire, configure o
BaseAddressdo cliente para o nome lógico do serviço de destino (ex:https+http://meu-servico).
Com este conhecimento, você está agora totalmente equipado para construir a camada de comunicação de suas aplicações distribuídas seguindo as melhores práticas da indústria.
Próxima lição:
Até agora, assumimos que cada serviço expõe apenas um endpoint HTTP. No entanto, um serviço pode precisar expor múltiplas portas para diferentes propósitos (ex: uma porta para HTTP e outra para gRPC). Na próxima lição, aprenderemos a usar endpoints nomeados para expor e consumir múltiplas portas de um único serviço, adicionando ainda mais flexibilidade à nossa arquitetura.