Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 49

Aspire and Service Discovery

Recommended resources

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 7123 na 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:

  1. Declaração de Dependência no AppHost: O AppHost define o grafo de dependências da sua aplicação.
  2. 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.
  3. 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:

  1. Inicia o serviço productsApi, que recebe um endereço dinâmico, por exemplo, https://localhost:7123.
  2. Ao iniciar o serviço store, por causa da chamada .WithReference(), o AppHost injeta variáveis de ambiente especiais no ambiente do store. Essas variáveis seguem um padrão:
    • services__products__https__0 = https://localhost:7123
    • services__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:

  1. Um HttpMessageHandler especial, adicionado pelo pacote Microsoft.Extensions.ServiceDiscovery (que faz parte dos ServiceDefaults), intercepta a requisição.
  2. Ele reconhece que products é um nome de serviço lógico.
  3. Ele consulta as variáveis de ambiente em busca de services__products__....
  4. Ele encontra o endereço real (ex: https://localhost:7123) e substitui https://products por ele, enviando a requisição para https://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.

.NET Aspire Service Discovery Flow
Este diagrama ilustra o fluxo completo do service discovery no Aspire: 1. O AppHost define os serviços. 2. A dependência é declarada com `.WithReference()`. 3. O AppHost injeta a URL da API como uma variável de ambiente no Frontend. 4. O Frontend configura seu HttpClient com o nome lógico. 5. O manipulador de service discovery resolve o nome para a URL real. 6. A comunicação acontece.

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.

.NET Aspire Dashboard: Service Resources and Endpoints
Nesta imagem do Aspire Dashboard, observe como o serviço `myweatherapp` tem variáveis de ambiente injetadas (`services_api_http_0` e `services_api_https_0`) que apontam para os endpoints reais do serviço `api`, demonstrando o mecanismo de service discovery em ação.
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 um HttpMessageHandler do 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.