Olá! Bem-vindo de volta ao nosso curso.
Na lição anterior, exploramos como o .NET Aspire automatiza o registro de health checks, garantindo que saibamos se cada serviço e suas dependências estão saudáveis e prontos para operar. Isso responde à pergunta: "O serviço está funcionando?". Agora, avançamos para o próximo desafio lógico em um sistema distribuído: "Como um serviço encontra outro para se comunicar?".
Esta lição marca o início do nosso módulo sobre Comunicação e Resiliência de Serviços. Nosso objetivo de aprendizagem para hoje é: Explicar como o Aspire facilita a descoberta de serviços (service discovery) para a comunicação entre eles. Você entenderá o mecanismo que permite que os serviços se comuniquem uns com os outros por meio de nomes lógicos, eliminando a necessidade de "hard-coding" de endereços IP e portas, um pilar fundamental para a criação de aplicações distribuídas robustas e flexíveis.
O Problema: A Fragilidade dos Endpoints "Hard-coded"
Imagine um cenário comum: você tem um serviço de frontend (uma Blazor Web App, por exemplo) que precisa consumir dados de uma API de backend. Em um desenvolvimento simples, a abordagem mais direta seria configurar a URL da API no arquivo appsettings.json do frontend:
{
"WeatherApi": {
"BaseAddress": "https://localhost:7123"
}
}
Embora funcional para um protótipo local, essa abordagem se torna um grande obstáculo em ambientes do mundo real por várias razões:
- Portas Dinâmicas: Em ambientes de contêineres, as portas são frequentemente atribuídas dinamicamente. A porta
7123na sua máquina pode ser diferente em outro ambiente ou mesmo em outra execução. - Múltiplos Ambientes: Manter arquivos de configuração diferentes para desenvolvimento, teste, homologação e produção é complicado e propenso a erros.
- Escalabilidade: Se você escalar sua API para múltiplas instâncias, para qual delas o frontend deve apontar? É necessário um balanceador de carga, o que adiciona outra camada de endereço fixo para gerenciar.
O vídeo a seguir, de Milan Jovanović, introduz exatamente esse problema, mostrando como a comunicação entre serviços com URLs fixas funciona e por que ela não é flexível.
Stop Hardcoding Service URLs! Service Discovery in .NET Made Easy With Aspire
Este vídeo, intitulado "Stop Hardcoding Service URLs!", estabelece o cenário de comunicação entre microsserviços e ilustra as limitações de usar URLs fixas.
Assista ao trecho inicial, de 00:00:00 a 00:03:05. O objetivo é entender o problema fundamental que o service discovery se propõe a resolver. Observe como o código utiliza um endereço hard-coded para a comunicação entre duas APIs.
A solução para essa rigidez é um mecanismo dinâmico chamado Service Discovery. A ideia é simples: em vez de os serviços conhecerem os endereços físicos uns dos outros, eles se referem uns aos outros por um nome lógico (ex: users-api), e uma infraestrutura se encarrega de traduzir esse nome lógico para um endereço de rede real em tempo de execução.
A Solução do Aspire: Service Discovery via Orquestração
O .NET Aspire implementa o service discovery de uma maneira elegante, integrando-o diretamente ao seu modelo de orquestração no AppHost. Você não precisa de um "servidor de registro" externo como Consul ou Eureka para o desenvolvimento local; o próprio AppHost atua como a fonte da verdade.
O mecanismo se baseia em três conceitos principais:
- Declaração de Dependência no AppHost: O AppHost define o grafo de dependências da sua aplicação.
- Injeção de Configuração por Variáveis de Ambiente: O AppHost injeta os endereços reais dos serviços nas variáveis de ambiente dos serviços que os consomem.
- Resolução no Cliente: Uma biblioteca no lado do cliente (incluída pelos
ServiceDefaults) sabe como ler essas variáveis de ambiente e resolver os nomes lógicos para os endereços físicos.
1. Declarando a Dependência com WithReference()
O ponto de partida é o arquivo Program.cs do seu projeto AppHost. Quando um serviço precisa se comunicar com outro, você expressa essa dependência usando o método de extensão .WithReference().
Veja um exemplo:
// Program.cs do projeto .AppHost
var builder = DistributedApplication.CreateBuilder(args);
// 1. O serviço de backend (API) é definido e recebe um nome lógico: "products"
var productsApi = builder.AddProject<Projects.MyStore_ProductsApi>("products");
// 2. O serviço de frontend é definido e...
var frontend = builder.AddProject<Projects.MyStore_Store>("store")
// 3. ...declaramos que ele tem uma referência para a API "products".
.WithReference(productsApi);
builder.Build().Run();
Ao usar .WithReference(productsApi), você está dizendo ao Aspire: "O serviço store precisa conhecer o endereço do serviço products."
2. Consumindo o Serviço com um Nome Lógico
Agora, no código do serviço store (o consumidor), em vez de usar uma URL fixa, você configura seu HttpClient para usar um URI especial que combina o esquema (http ou https) com o nome lógico do serviço de destino.
// Program.cs do projeto 'store' (consumidor)
// Configura um HttpClient para se comunicar com a API
builder.Services.AddHttpClient<ProductsClient>(client =>
{
// Usa um nome lógico, não um endereço físico!
// "https+http" tenta HTTPS primeiro, depois fallback para HTTP.
client.BaseAddress = new Uri("https+http://products");
});
A string "https+http://products" não é uma URL válida para a internet. É uma convenção que o mecanismo de service discovery do Aspire entende.
3. O Mecanismo por Baixo dos Panos: Variáveis de Ambiente
Aqui está a parte crucial, o "como funciona" que é tão importante para um entendimento profundo.
Quando o AppHost inicia, ele faz o seguinte:
- Inicia o serviço
productsApi, que recebe um endereço dinâmico, por exemplo,https://localhost:7123. - Ao iniciar o serviço
store, por causa da chamada.WithReference(), o AppHost injeta variáveis de ambiente especiais no ambiente dostore. Essas variáveis seguem um padrão:services__products__https__0=https://localhost:7123services__products__http__0=http://localhost:5123
Quando o HttpClient no serviço store tenta fazer uma requisição para https://products/api/items, o seguinte acontece:
- Um
HttpMessageHandlerespecial, adicionado pelo pacoteMicrosoft.Extensions.ServiceDiscovery(que faz parte dosServiceDefaults), intercepta a requisição. - Ele reconhece que
productsé um nome de serviço lógico. - Ele consulta as variáveis de ambiente em busca de
services__products__.... - Ele encontra o endereço real (ex:
https://localhost:7123) e substituihttps://productspor ele, enviando a requisição parahttps://localhost:7123/api/items.
O vídeo a seguir, de James Montemagno, demonstra exatamente esse fluxo. Ele adiciona uma referência e depois inspeciona o Aspire Dashboard para mostrar as variáveis de ambiente que foram injetadas.
Build Better Apps with .NET Aspire - Complete Beginner's Guide & Tutorial
Neste trecho do seu guia sobre .NET Aspire, James Montemagno explica e demonstra de forma muito clara como o service discovery funciona, desde a configuração no AppHost até a verificação do resultado no dashboard.
Assista ao trecho de 00:27:02 a 00:31:33. Preste atenção especial em como ele usa .WithReference() no AppHost e, em seguida, navega para os detalhes do serviço no Aspire Dashboard para mostrar as variáveis de ambiente services_products_... que foram criadas automaticamente.
Para reforçar o que você viu, este artigo do Telerik fornece uma excelente explicação textual do mesmo mecanismo.
.NET Aspire 5: Orchestration and Service Discovery
Este artigo descreve a comunicação entre dois APIs, Orders e Inventory, e explica como o service discovery do Aspire as conecta.
Leia a seção "How Does Orders Talk to Inventory?". Ela mostra o URI https+http://inventory e detalha exatamente as variáveis de ambiente que o Aspire injeta, consolidando o conhecimento do vídeo.
Visualizando o Fluxo Completo
Vamos resumir o processo inteiro com um diagrama.

No Aspire Dashboard, você pode ver essa mágica acontecer. Ao inspecionar um serviço que consome outro, você encontrará as variáveis de ambiente que o Aspire injetou.

Teste seu entendimento!
Você tem dois serviços, users-api e orders-api. O orders-api precisa chamar o users-api. No seu AppHost, você tem a seguinte configuração:var usersApi = builder.AddProject<UsersApi>("users");builder.AddProject<OrdersApi>("orders").WithReference(usersApi);
No projeto orders-api, você configura um HttpClient com o endereço base http://users. Qual das seguintes afirmações descreve corretamente como isso funciona em tempo de execução?
a) O orders-api realiza uma consulta DNS para o hostname "users".
b) O AppHost modifica o código compilado do orders-api para substituir "http://users" pela URL correta.
c) O AppHost injeta uma variável de ambiente no contêiner do orders-api, e um manipulador de mensagens especial usa essa variável para resolver a URL final.
d) O HttpClient envia uma mensagem de broadcast na rede perguntando pela localização do serviço "users".
Show answer
A resposta correta é a (c). O mecanismo de service discovery do Aspire baseia-se na orquestração, onde o AppHost injeta os endpoints como variáveis de ambiente, e um HttpMessageHandler no lado do cliente as utiliza para resolver o nome lógico para o endereço físico no momento da chamada HTTP.
Conclusão
Nesta lição, você aprendeu como o .NET Aspire simplifica drasticamente a comunicação entre serviços. Ao abstrair os endereços de rede por trás de nomes lógicos, o Aspire cria aplicações mais robustas, portáteis e fáceis de manter.
Principais Pontos:
- Service Discovery resolve o problema de endpoints fixos em sistemas distribuídos, permitindo que os serviços se encontrem dinamicamente.
- O Aspire implementa um modelo de service discovery baseado em orquestração, onde o AppHost é a fonte da verdade para o ambiente de desenvolvimento.
- O método
.WithReference()no AppHost é a chave para estabelecer uma dependência de comunicação entre dois serviços. - O mecanismo subjacente depende da injeção de variáveis de ambiente (com o formato
services__{nome-logico}__{protocolo}__{indice}) e de umHttpMessageHandlerdo lado do cliente que traduz nomes lógicos em endereços físicos.
Com este conhecimento, você está agora equipado para construir aplicações com múltiplos serviços que se comunicam de forma transparente, independentemente de estarem rodando na sua máquina local ou em um ambiente de nuvem complexo.
Próxima lição:
Agora que entendemos como os serviços se encontram, vamos aprofundar em como implementar essa comunicação de forma limpa e segura no código .NET. A próxima lição abordará como configurar e injetar HttpClients tipados para comunicação entre serviços usando os padrões de referência de serviço do Aspire, uma prática recomendada para um código mais manutenível e testável.