.NET .NET Aspire Microsserviços DevEx Observabilidade Docker Desenvolvimento Local

.NET Aspire: orquestre microsserviços localmente sem Docker Compose manual

Guia prático do .NET Aspire para desenvolvimento local: AppHost com orquestração de serviços, integrações com Redis, PostgreSQL e RabbitMQ.

N
Neryx Digital Architects
29 de outubro de 2025
12 min de leitura
240 profissionais leram
Categoria: Arquitetura Público: Times de plataforma e operação Etapa: Decisão

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.

Quer sair do modo reativo e priorizar o que mais importa?

O diagnóstico de maturidade ajuda a transformar sintomas operacionais em um plano mais claro de evolução.

Avaliar maturidade

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.