Saltar al contenido principal

.NET Aspire 🔴

.NET Aspire es el stack oficial de Microsoft para desarrollar aplicaciones cloud-native con .NET. No es un framework nuevo — es una capa de orquestación que resuelve los problemas más dolorosos del desarrollo local con microservicios: levantar múltiples servicios, service discovery, variables de entorno, conexiones a bases de datos, y observabilidad. Disponible desde .NET 8.


¿Qué problema resuelve?

Sin Aspire:                         Con Aspire:
────────────────────────────── ──────────────────────────────
docker-compose up (5 archivos) → dotnet run en el AppHost

Hardcodear connection strings → Service discovery automático
en cada proyecto (http://nombre-servicio)

Configurar Jaeger + Prometheus → Dashboard integrado con
manualmente trazas, métricas y logs

Variables de entorno por → Configuración centralizada
cada servicio en el AppHost

"En mi máquina funciona" → Misma configuración para
local, CI, y producción

Estructura de un proyecto Aspire

MiApp.AppHost/          ← Orquestador — define qué corre y cómo
Program.cs
MiApp.AppHost.csproj

MiApp.ServiceDefaults/ ← Configuración compartida (telemetría, health checks)
Extensions.cs
MiApp.ServiceDefaults.csproj

MiApp.ApiService/ ← Tu API (ASP.NET Core normal)
Program.cs

MiApp.Worker/ ← Tu worker (Background Service normal)
Program.cs

MiApp.Web/ ← Tu frontend (Blazor, React via proxy, etc.)
Program.cs

AppHost — el orquestador

// MiApp.AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);

// 1. INFRAESTRUCTURA — bases de datos, caches, colas
var redis = builder.AddRedis("cache")
.WithRedisCommander(); // UI de Redis en desarrollo

var postgres = builder.AddPostgres("db")
.WithPgAdmin() // pgAdmin en desarrollo
.AddDatabase("appdb"); // crea la base de datos "appdb"

var rabbit = builder.AddRabbitMQ("messaging")
.WithManagementPlugin(); // RabbitMQ Management UI

// 2. SERVICIOS — tus proyectos
var apiService = builder.AddProject<Projects.MiApp_ApiService>("api")
.WithReference(redis) // inyecta la connection string de Redis
.WithReference(postgres) // inyecta la connection string de Postgres
.WithReference(rabbit) // inyecta la connection string de RabbitMQ
.WithHttpHealthCheck("/health");

var worker = builder.AddProject<Projects.MiApp_Worker>("worker")
.WithReference(postgres)
.WithReference(rabbit);

// 3. FRONTEND
builder.AddProject<Projects.MiApp_Web>("webfrontend")
.WithReference(apiService) // webfrontend puede hablar con api
.WithExternalHttpEndpoints(); // expuesto fuera de la red Aspire

builder.Build().Run();
Qué hace WithReference

WithReference inyecta automáticamente las connection strings y URLs como variables de entorno con el nombre correcto. No hardcodeás nada — Aspire genera ConnectionStrings__cache, services__api__http__0, etc.


ServiceDefaults — configuración compartida

// MiApp.ServiceDefaults/Extensions.cs
// Este archivo se genera automáticamente — no modificar salvo que necesitás personalizar

public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(
this IHostApplicationBuilder builder)
{
// OpenTelemetry — trazas, métricas y logs unificados
builder.ConfigureOpenTelemetry();

// Health checks estándar
builder.AddDefaultHealthChecks();

// Service discovery — permite resolver "http://api" a la URL real
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
http.AddStandardResilienceHandler(); // retry + circuit breaker automático
http.AddServiceDiscovery();
});

return builder;
}

public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// /health — usado por Aspire para saber si el servicio está listo
// /alive — liveness
app.MapHealthChecks("/health");
app.MapHealthChecks("/alive", new() { Predicate = r => r.Tags.Contains("live") });

return app;
}
}
// En cada servicio — una sola línea activa todo
// MiApp.ApiService/Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults(); // ← OpenTelemetry + health checks + service discovery

// Tu configuración normal...
builder.Services.AddControllers();

var app = builder.Build();

app.MapDefaultEndpoints(); // ← /health y /alive
app.MapControllers();
app.Run();

Service Discovery — comunicación entre servicios

Aspire inyecta las URLs de los servicios automáticamente. No hay hardcoding de puertos ni IPs.

// En el ApiService, llamar al Worker o a otro servicio:
builder.Services.AddHttpClient<CatalogoClient>(client =>
{
// "http://catalogo" → Aspire resuelve automáticamente a la URL real
client.BaseAddress = new Uri("http://catalogo");
});

// En producción (Azure Container Apps) funciona igual —
// Aspire configura el service discovery en el entorno de destino

Componentes — integración con infraestructura

Aspire tiene paquetes listos para las tecnologías más comunes. Cada uno:

  • Registra el cliente correctamente en DI
  • Agrega health checks automáticos
  • Configura OpenTelemetry para tracing
  • Lee la connection string desde la configuración de Aspire
<!-- En el proyecto que necesita Redis -->
<PackageReference Include="Aspire.StackExchange.Redis" Version="8.2.0" />

<!-- Para Postgres con EF Core -->
<PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.2.0" />

<!-- Para SQL Server con EF Core -->
<PackageReference Include="Aspire.Microsoft.EntityFrameworkCore.SqlServer" Version="8.2.0" />

<!-- Para Azure Service Bus -->
<PackageReference Include="Aspire.Azure.Messaging.ServiceBus" Version="8.2.0" />

<!-- Para Azure Blob Storage -->
<PackageReference Include="Aspire.Azure.Storage.Blobs" Version="8.2.0" />
// ApiService/Program.cs — usar los componentes Aspire
var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Redis — registra IConnectionMultiplexer + IDistributedCache + health check
builder.AddRedisDistributedCache("cache"); // "cache" = nombre en el AppHost

// EF Core con Postgres — registra DbContext + health check + retry automático
builder.AddNpgsqlDbContext<AppDbContext>("appdb"); // "appdb" = nombre en el AppHost

// Azure Service Bus
builder.AddAzureServiceBusClient("messaging");

Dashboard — observabilidad local

Al correr el AppHost, Aspire levanta un dashboard en http://localhost:15888 con:

┌─────────────────────────────────────────────────────┐
│ .NET Aspire Dashboard │
├─────────────────────────────────────────────────────┤
│ Resources │
│ ├── api ● Running → http://localhost:5001│
│ ├── worker ● Running │
│ ├── webfrontend ● Running → http://localhost:3000│
│ ├── cache ● Running → Redis │
│ └── db ● Running → Postgres │
├─────────────────────────────────────────────────────┤
│ Structured Logs │ Traces │ Metrics │
│ │ │ │
│ Todos los logs │ Trazas │ CPU, memoria, │
│ de todos los │ distrib.│ request rate, │
│ servicios en │ end-to- │ error rate │
│ un solo lugar │ end │ │
└─────────────────────────────────────────────────────┘

Sin configuración extra, tenés:

  • Logs estructurados de todos los servicios en un solo lugar
  • Trazas distribuidas end-to-end (request que pasa por 3 servicios)
  • Métricas de runtime (.NET y HTTP)
  • Estado y variables de entorno de cada recurso

Persistencia de datos en desarrollo

Por defecto, los contenedores de Aspire son efímeros — los datos se pierden al reiniciar. Para persistir:

// AppHost/Program.cs
var postgres = builder.AddPostgres("db")
.WithDataVolume("mi-app-postgres-data") // volumen Docker nombrado
.AddDatabase("appdb");

var redis = builder.AddRedis("cache")
.WithDataVolume("mi-app-redis-data");

Ejecutar comandos en contenedores (migraciones)

// AppHost/Program.cs — ejecutar migraciones de EF Core automáticamente
var postgres = builder.AddPostgres("db").AddDatabase("appdb");

// Proyecto de migraciones que corre antes del api
builder.AddProject<Projects.MiApp_Migrations>("migrations")
.WithReference(postgres)
.WaitFor(postgres); // esperar que Postgres esté listo

// La API espera a que las migraciones terminen
var api = builder.AddProject<Projects.MiApp_ApiService>("api")
.WithReference(postgres)
.WaitForCompletion("migrations"); // esperar que termine el job
// MiApp.Migrations/Program.cs — proyecto de migración simple
var builder = Host.CreateApplicationBuilder(args);

builder.AddServiceDefaults();
builder.AddNpgsqlDbContext<AppDbContext>("appdb");

var host = builder.Build();

using var scope = host.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

await db.Database.MigrateAsync();

Console.WriteLine("Migraciones aplicadas correctamente.");
// Al terminar el proceso, WaitForCompletion lo detecta

Deploy a Azure Container Apps

Aspire tiene integración directa con Azure Developer CLI (azd):

# 1. Instalar Azure Developer CLI
winget install microsoft.azd

# 2. Inicializar el proyecto para Azure
azd init

# 3. Provisionar infraestructura y desplegar
azd up
# Esto:
# - Crea Azure Container Apps Environment
# - Crea Azure Container Registry
# - Conteneriza cada servicio
# - Despliega y configura service discovery en la nube
# - Configura Azure Monitor / Application Insights automáticamente

# 4. Updates posteriores
azd deploy

El mismo AppHost que usás localmente define lo que se despliega en Azure — un único source of truth.


Aspire vs Docker Compose

.NET AspireDocker Compose
Lenguaje de configC# tipadoYAML
Service discoveryAutomáticoManual (nombres de servicio)
ObservabilidadDashboard integradoManual (stack ELK/Grafana)
Health checksIntegradosManual
Deploy a cloudazd up (Azure)Requiere CI/CD custom
RefactoringRename en IDEBuscar/reemplazar en YAML
Curva de aprendizajeBaja (es C#)Media
MulticloudSolo Azure nativoCualquier cloud
Ideal paraApps .NET con AzureStacks heterogéneos, multicloud
Cuándo elegir qué

Aspire si tu stack es .NET + Azure. Docker Compose si tenés servicios en múltiples lenguajes o necesitás portabilidad entre clouds. No son excluyentes — Aspire puede usar imágenes Docker directamente.


Integrar un servicio externo (imagen Docker)

// AppHost/Program.cs — agregar servicios que no son proyectos .NET
var builder = DistributedApplication.CreateBuilder(args);

// Imagen Docker directa
var jaeger = builder.AddContainer("jaeger", "jaegertracing/all-in-one")
.WithHttpEndpoint(port: 16686, targetPort: 16686, name: "ui")
.WithEndpoint(port: 4317, targetPort: 4317, name: "otlp");

// Keycloak para auth
var keycloak = builder.AddContainer("keycloak", "quay.io/keycloak/keycloak", "24.0")
.WithArgs("start-dev")
.WithHttpEndpoint(port: 8080, targetPort: 8080)
.WithEnvironment("KEYCLOAK_ADMIN", "admin")
.WithEnvironment("KEYCLOAK_ADMIN_PASSWORD", "admin");

var api = builder.AddProject<Projects.MiApp_ApiService>("api")
.WithReference(jaeger.GetEndpoint("otlp"))
.WithReference(keycloak.GetEndpoint("http"));

Preguntas frecuentes de entrevista 🎯

1. ¿Qué es .NET Aspire y qué problema resuelve?

Aspire es un stack de Microsoft para desarrollar aplicaciones cloud-native con .NET. Resuelve el problema de la complejidad de desarrollo local con microservicios: en lugar de mantener docker-compose, scripts de setup, configuración manual de connection strings y herramientas de observabilidad separadas, Aspire los unifica en un AppHost escrito en C#. Al correr el AppHost, levanta todos los servicios, inyecta las configuraciones automáticamente, y provee un dashboard con logs, trazas y métricas de todos los servicios en un solo lugar.

2. ¿Qué es el AppHost y qué es ServiceDefaults?

El AppHost es el proyecto orquestador — define qué servicios corren, qué infraestructura necesitan (Redis, Postgres), y cómo se relacionan entre sí. Es el único lugar con conocimiento de la topología. ServiceDefaults es un proyecto de librería compartida que configura automáticamente OpenTelemetry, health checks y service discovery en cada servicio — con AddServiceDefaults() en una línea se activa todo.

3. ¿Cómo funciona el service discovery en Aspire?

Aspire inyecta las URLs de los servicios como variables de entorno (services__nombre__http__0). El cliente de service discovery las resuelve cuando hacés new Uri("http://nombre-servicio"). En desarrollo apunta a localhost:puerto-aleatorio. En producción con Azure Container Apps, Aspire configura el mismo mecanismo de discovery del entorno. El código del servicio nunca hardcodea URLs ni puertos.

4. ¿Qué ventaja tiene configurar Aspire en C# en lugar de YAML?

Al ser C# tipado, el compilador valida la configuración, tenés autocompletado e IntelliSense, podés usar lógica condicional (if (builder.Environment.IsDevelopment())), y los renames en el IDE actualizan todo el proyecto. Con YAML, un typo en el nombre de un servicio es un error silencioso que aparece en runtime.

5. ¿Aspire reemplaza Kubernetes?

No — son para niveles diferentes. Aspire es para el ciclo de desarrollo local y opcionalmente para deploy a Azure Container Apps. Kubernetes es el orquestador de producción para escenarios complejos con múltiples réplicas, rolling updates, HPA, etc. La integración típica es: Aspire para dev local + Azure Container Apps para producción (Aspire genera la infraestructura via azd up). Si necesitás K8s específicamente, Aspire puede generar los manifiestos con azd también.