Rodar microsserviços localmente tem um custo alto de setup: um docker-compose.yml que ninguém mantém atualizado, variáveis de ambiente espalhadas em múltiplos .env, strings de conexão hardcoded que diferem entre máquinas, e o clássico "funciona na minha máquina". O .NET Aspire foi criado para resolver exatamente isso — orquestração de microsserviços em desenvolvimento com observabilidade embutida, escrita em C# como primeiro cidadão.
O que é o .NET Aspire
.NET Aspire é um stack opinativo para aplicações distribuídas em .NET. Não é um framework de runtime — é uma camada de desenvolvimento e orquestração que opera em três frentes: o AppHost (projeto C# que descreve toda a topologia da sua aplicação), os service defaults (configurações compartilhadas de health checks, OpenTelemetry e service discovery), e os componentes de integração (pacotes que conectam Redis, PostgreSQL, RabbitMQ, Azure Service Bus etc. com configuração mínima).
O resultado prático: um dotnet run no projeto AppHost sobe todos os serviços, provisiona os recursos de infraestrutura (banco, cache, broker), injeta as strings de conexão automaticamente em cada serviço, e abre um dashboard com traces distribuídos, logs centralizados e métricas em tempo real.
Pré-requisitos e instalação
# .NET 8 ou 9 — recomendado .NET 9
dotnet --version # 9.0.x
# Docker Desktop (ou Podman) — para os recursos de infraestrutura
docker --version
# Workload do Aspire
dotnet workload install aspire
# Verifica instalação
dotnet workload list | grep aspire
Estrutura de um projeto com Aspire
MeuProjeto/
├── MeuProjeto.AppHost/ ← Projeto de orquestração (Aspire)
│ ├── Program.cs ← Topologia completa da aplicação
│ └── MeuProjeto.AppHost.csproj
├── MeuProjeto.ServiceDefaults/ ← Configurações compartilhadas
│ ├── Extensions.cs ← Health checks, OpenTelemetry, service discovery
│ └── MeuProjeto.ServiceDefaults.csproj
├── OrderService/ ← Microsserviço de pedidos
│ ├── Program.cs
│ └── OrderService.csproj
├── StockService/ ← Microsserviço de estoque
│ ├── Program.cs
│ └── StockService.csproj
├── NotificationService/ ← Microsserviço de notificações
│ ├── Program.cs
│ └── NotificationService.csproj
└── MeuProjeto.sln
AppHost: a topologia completa em C#
// MeuProjeto.AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);
// ── Infraestrutura ────────────────────────────────────────────────────────────
// PostgreSQL: sobe um container automaticamente em desenvolvimento
var postgres = builder.AddPostgres("postgres")
.WithDataVolume("postgres-data") // persiste dados entre restarts
.WithPgAdmin(); // abre pgAdmin no browser
var ordersDb = postgres.AddDatabase("orders-db");
var stockDb = postgres.AddDatabase("stock-db");
// Redis: cache distribuído
var redis = builder.AddRedis("redis")
.WithRedisInsight(); // abre RedisInsight no browser
// RabbitMQ: broker de mensagens
var rabbitmq = builder.AddRabbitMQ("rabbitmq")
.WithManagementPlugin(); // abre Management UI no browser
// ── Serviços ──────────────────────────────────────────────────────────────────
var orderService = builder.AddProject<Projects.OrderService>("order-service")
.WithReference(ordersDb) // injeta connection string como env var
.WithReference(redis) // injeta Redis connection string
.WithReference(rabbitmq) // injeta RabbitMQ connection string
.WithHttpHealthCheck("/health/ready")
.WithReplicas(1); // pode escalar para múltiplas instâncias
var stockService = builder.AddProject<Projects.StockService>("stock-service")
.WithReference(stockDb)
.WithReference(rabbitmq)
.WithHttpHealthCheck("/health/ready");
var notificationService = builder.AddProject<Projects.NotificationService>("notification-service")
.WithReference(rabbitmq)
.WithEnvironment("SMTP_HOST", "maildev") // variável de ambiente adicional
.WithHttpHealthCheck("/health/ready");
// API Gateway / BFF que agrega os serviços
builder.AddProject<Projects.ApiGateway>("api-gateway")
.WithReference(orderService) // service discovery automático
.WithReference(stockService)
.WithExternalHttpEndpoints(); // expõe para o browser do dev
builder.Build().Run();
Service Defaults: configurações compartilhadas uma vez
// MeuProjeto.ServiceDefaults/Extensions.cs
// Cada microsserviço chama AddServiceDefaults() no seu Program.cs
// e recebe tudo isso de graça
public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(
this IHostApplicationBuilder builder)
{
// OpenTelemetry: traces, métricas e logs — visíveis no dashboard do Aspire
builder.ConfigureOpenTelemetry();
// Health checks padrão (liveness + readiness)
builder.AddDefaultHealthChecks();
// Service discovery: resolve nomes de serviço para URLs em desenvolvimento
// Em produção, pode ser substituído por Kubernetes DNS ou Consul
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
// Resilience padrão para todos os HttpClients
http.AddStandardResilienceHandler();
// Usa service discovery para resolver URLs por nome
http.AddServiceDiscovery();
});
return builder;
}
public static IHostApplicationBuilder ConfigureOpenTelemetry(
this IHostApplicationBuilder builder)
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation();
});
builder.AddOpenTelemetryExporters();
return builder;
}
private static IHostApplicationBuilder AddOpenTelemetryExporters(
this IHostApplicationBuilder builder)
{
// Em desenvolvimento: exporta para o dashboard do Aspire (OTLP)
// Em produção: pode ser Grafana, Datadog, Azure Monitor etc.
var useOtlpExporter = !string.IsNullOrWhiteSpace(
builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
if (useOtlpExporter)
builder.Services.AddOpenTelemetry().UseOtlpExporter();
return builder;
}
public static IHostApplicationBuilder AddDefaultHealthChecks(
this IHostApplicationBuilder builder)
{
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(),
tags: ["live"]);
return builder;
}
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Endpoint de liveness — apenas "estou vivo"
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});
// Endpoint de readiness — todos os checks (banco, broker etc.)
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = _ => true
});
return app;
}
}
Microsserviço: integração com Aspire em 3 linhas
// OrderService/Program.cs
// O Aspire injetou as connection strings como variáveis de ambiente.
// Os componentes de integração as leem automaticamente pelo nome do recurso.
var builder = WebApplication.CreateBuilder(args);
// Configurações compartilhadas: health checks, OpenTelemetry, service discovery
builder.AddServiceDefaults();
// Integração com PostgreSQL — lê a connection string injetada pelo AppHost
// Nome "orders-db" é o mesmo definido no AppHost
builder.AddNpgsqlDbContext<OrdersDbContext>("orders-db", settings =>
{
settings.DisableRetry = false;
});
// Integração com Redis — lê a connection string injetada pelo AppHost
builder.AddRedisClient("redis");
// Integração com RabbitMQ via MassTransit
builder.Services.AddMassTransit(x =>
{
x.AddConsumers(typeof(Program).Assembly);
x.UsingRabbitMq((context, cfg) =>
{
// Service name "rabbitmq" é resolvido pelo service discovery do Aspire
cfg.Host("rabbitmq");
cfg.ConfigureEndpoints(context);
});
});
builder.Services.AddControllers();
var app = builder.Build();
app.MapDefaultEndpoints(); // /health/live e /health/ready
app.MapControllers();
app.Run();
O dashboard do Aspire
Ao rodar o AppHost com dotnet run, o Aspire abre automaticamente um dashboard em https://localhost:15888. O dashboard mostra todos os serviços e recursos com status de saúde em tempo real, traces distribuídos (você vê a requisição traversando OrderService → StockService → NotificationService com tempo em cada etapa), logs centralizados de todos os serviços em um único lugar (sem precisar abrir terminais separados), e métricas de HTTP, banco, memória e custom por serviço.
Para uma equipe acostumada a abrir quatro terminais para ver os logs de quatro serviços, isso muda o fluxo de desenvolvimento radicalmente.
Migrando de Docker Compose para Aspire
Se você já tem um docker-compose.yml, a migração para Aspire é incremental. Você não precisa migrar tudo de uma vez. Comece adicionando o AppHost ao projeto e migrando apenas os recursos de infraestrutura (banco, cache, broker) — os serviços ainda podem estar no Compose por um tempo. O ServiceDefaults pode ser adicionado a um serviço por vez.
A principal diferença conceitual: no Docker Compose você descreve containers. No Aspire você descreve recursos e referências entre eles em C#, e o Aspire decide como provisionar — containers em desenvolvimento, configuração de ambiente de staging, manifests de deployment em produção.
Aspire em CI/CD e produção
O Aspire é primariamente uma ferramenta de desenvolvimento local, mas tem um caminho para produção: o comando dotnet aspire publish gera manifests de deployment (atualmente Kubernetes via Helm e Azure Container Apps são os targets mais maduros). A geração de manifests é um ponto de partida, não um substituto completo para uma pipeline de CI/CD bem configurada.
Em CI/CD, o AppHost pode ser usado com Aspire.Hosting.Testing para testes de integração que sobem a aplicação completa: banco real, broker real, serviços reais — sem mocks, sem WireMock manual, sem fixtures complicadas.
// Testes de integração com DistributedApplicationTestingBuilder
public class OrderIntegrationTests : IAsyncLifetime
{
private DistributedApplication _app;
private HttpClient _client;
public async Task InitializeAsync()
{
// Sobe TODA a topologia do AppHost para testes
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MeuProjeto_AppHost>();
_app = await appHost.BuildAsync();
await _app.StartAsync();
// Obtém o HttpClient para o serviço desejado
_client = _app.CreateHttpClient("order-service");
}
[Fact]
public async Task CreateOrder_ValidRequest_ReturnsCreated()
{
var response = await _client.PostAsJsonAsync("/api/orders", new
{
CustomerId = Guid.NewGuid(),
Items = new[] { new { ProductId = Guid.NewGuid(), Quantity = 2, UnitPrice = 50.0m } }
});
response.StatusCode.Should().Be(HttpStatusCode.Created);
}
public async Task DisposeAsync()
=> await _app.DisposeAsync();
}
O .NET Aspire ainda está evoluindo rapidamente — a versão 9.x trouxe melhorias significativas na geração de manifests e nas integrações. Para projetos novos com múltiplos serviços, é hoje a melhor experiência de desenvolvimento local disponível no ecossistema .NET.
Arquitetura de microsserviços envolve muito mais do que o código — exige tooling certo para o time ser produtivo. Se você quer estruturar a experiência de desenvolvimento do seu time .NET, a Neryx pode ajudar.