¿Qué es .NET Aspire?
.NET Aspire presenta un marco de trabajo preparado para la nube y diseñado para el desarrollo de aplicaciones distribuidas. Las aplicaciones nativas de la nube suelen incluir pequeños componentes interconectados o microservicios. Estos a menudo dependen de una multitud de servicios, incluyendo bases de datos, sistemas de caché, mensajería, entre otros.
Al construir dichas aplicaciones, la comunicación entre estos microservicios, la escalabilidad, la resiliencia y la monitorización son nuestras pricipales preocupaciones. El conjunto de herramientas .NET Aspire ha sido creado con el propósito exclusivo de resolver todas estas cuestiones.
Las comunicaciones entre aplicaciones se resuelven a través del Descubrimiento de Servicios. También nos proporciona interfaces estandarizadas para servicios comúnmente utilizados, tales como caché y diferentes proveedores de bases de datos en la forma de distintos componentes que son accesibles a través de paquetes NuGet. Además, obtenemos plantillas de proyectos y un dashboard de control donde podemos monitorear nuestra aplicación distribuida con gran nivel de detalle.
Configuración del Proyecto y Nuevas Herramientas
Para utilizar las características de Aspire, necesitamos instalar las herramientas de .NET Aspire que cosnta de dependencias internas y plantillas de proyectos.
Vamos a instalarlo desde línea de comandos (CLI):
dotnet workload update
dotnet workload install aspire
Una vez instalado, podremos ver las plantillas: Aplicación .NET Aspire y Aplicación Inicial .NET Aspire. Para este artículo, utilizaremos la segunda opción que es la conocida aplicación Weather y nos proporciona por defecto cuatro proyectos:
- ApiService, nuestra minimal API.
- Un front-end Blazor en el proyecto Web.
- ServiceDefaults que es responsable de configurar la resiliencia, el descubrimiento de servicios y la monitorización para todos los proyectos en nuestra aplicación distribuida.
- AppHost une todo y actúa como un orquestador. Es nuestro proyecto de inicio y es responsable de ejecutar todo dentro de nuestra aplicación.
Lanza el proyecto:
Proyecto Tye ha muerto, larga vida a .NET Aspire
Los lectores de este blog podrían haber visto una entrada sobre el Proyecto Tye que publiqué hace algún tiempo: https://jmfloreszazo.com/introduccion-al-proyecto-tye/
Y tal vez se estén preguntando, ¿por qué me tomo un momento para hablar sobre este proyecto?
El motivo por el que Aspire adopta un enfoque distinto al del Proyecto Tye.
En el Proyecto Tye, la ejecución de servicios, contenedores y otros ejecutables, que se especifican en la configuración YAML del archivo tye.yaml, se orquestaba mediante código en C#.
El modelo de aplicación de Tye, al estar diseñado para ser consciente de la totalidad de los recursos que necesita orquestar, tiene conocimiento de todas las URLs, los puertos y las cadenas de conexión correspondientes. Esto le permite, por ejemplo, inyectar dicha información a través de variables de entorno.
Por otro lado, el modelo de aplicación de .NET Aspire guarda similitudes. Sin embargo, a diferencia de Tye, en Aspire el desarrollador declara sus recursos utilizando código en C# en lugar de código YAML.
Y mi aprecación personal, es que el Proyecto Tye esta ya descartado por parte de Microsoft y casi seguro es la fuente de inspiración para Aspire. He revisado el código fuente de ambos proyectos y es lo que me lleva a esa conclusión.
Creo que las lecciones aprendidas por Microsoft desde el año 2021 con el Proyecto Tye hasta hoy, posicionarán a .NET Aspire como una herramienta esencial, y no será un experimento como lo fue Tye.
Orquestación con .NET Aspire
Como ya sabemos, el proyecto AppHost es responsable de la orquestación dentro de nuestra aplicación. Veamos cómo funciona:
var builder = DistributedApplication.CreateBuilder(args);
var apiService = builder.AddProject<Projects.AspireSampleApp_ApiService>("apiservice");
builder.AddProject<Projects.AspireSampleApp_Web>("webfrontend")
.WithReference(apiService);
builder.Build().Run();
Todo sucede en el archivo Program.cs.
Primero, creamos un constructor de aplicaciones distribuidas utilizando el método CreateBuilder(). Despues, agregamos nuestros dos proyectos usando el método AddProject(), pasando el nombre del proyecto deseado como una cadena.
Actualmente, nuestra API no necesita saber acerca de ningún otro proyecto, por lo que su configuración solo depende del método AddProject(). Sin embargo, nuestro front-end depende estrictamente de la API para obtener sus datos, así que después de agregarlo a nuestra instancia del constructor también usamos el método WithReference() para registrar la API.
Finalmente, utilizamos los métodos Build() y Run() para construir y ejecutar nuestra aplicación distribuida.
Configuraciones Globales de Servicios
El proyecto ServiceDefaults se encarga de todas nuestras configuraciones globales:
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
// Turn on resilience by default
http.AddStandardResilienceHandler();
// Turn on service discovery by default
http.UseServiceDiscovery();
});
return builder;
}
Todo sucede en el método de extensión AddServiceDefaults() sobre IHostApplicationBuilder en la clase Extensions. Aquí, agregamos y configuramos varios servicios a nuestra instancia del constructor.
Comenzamos con los métodos ConfigureOpenTelemetry() y AddDefaultHealthChecks() que están definidos en la misma clase.
Después de estos pasos iniciales, continuamos mejorando nuestra colección IServiceCollection al incorporar el Descubrimiento de Servicios usando el método AddServiceDiscovery(). Es crucial añadir UseServiceDiscovery() a nuestro cliente HTTP para que el Descubrimiento de Servicios funcione.
De forma adicional: habilitamos por defecto la resiliencia para nuestro cliente HTTP con el método AddStandardResilienceHandler().
Y sí, puedes trabajar de forma muy sencilla con OpenTelemetry. De aquí mi insistencia en artículos pasados. Es una herramienta que debemos tener en nuestra caja de herramientas; tambien puedes usar Aspire con Azure Application Insights
Componentes
Los componentes .NET Aspire comprenden diversos paquetes NuGet que facilitan la integración de aplicaciones distribuidas con servicios y plataformas reconocidas.
Los paquetes NuGet se extienden a servicios bien conocidos como el caché de Redis, múltiples proveedores de bases de datos y varios de los servicios de Azure.
Cada componente está diseñado para proporcionar funcionalidades esenciales para aplicaciones nativas de la nube, lo cual se logra mediante la provisión automatizada o el seguimiento de patrones de configuración estandarizados.
Si por ejemplo quiero añadir funcionalidad con Redis:
var builder = DistributedApplication.CreateBuilder(args);
var apiservice = builder.AddProject<Projects.AspireDistributedApp_ApiService>("apiservice");
var redisCache = builder.AddRedisContainer("cache");
builder.AddProject<Projects.AspireDistributedApp_Web>("webfrontend")
.WithReference(apiservice)
.WithReference(redisCache);
builder.Build().Run();
En Program de AppHost, utilizamos el método AddRedisContainer(), pasando el nombre «cache» para registrar un contenedor de Redis.
El siguiente paso es añadirlo como referencia en nuestro proyecto de front-end.
Si quieres probar esto debes ir a contenedores, en este caso se requerirá Docker para que .NET Aspire pueda ejecutar el contenedor de caché.
A continuación, agregamos un paquete NuGet a nuestro proyecto Web:
dotnet add package Aspire.StackExchange.Redis.OutputCaching --prerelease
Y en la aplicación deberías usar:
builder.AddRedisOutputCache("cache");
app.UseOutputCache();
@attribute [OutputCache(Duration = 60)]
Conclusión
Como hemos visto, .NET Aspire facilita significativamente el desarrollo de aplicaciones distribuidas, superando a herramientas como el Proyecto Tye en varios aspectos.
Destaca por su diseño orientado a la nube y su adaptabilidad para el desarrollo de microservicios, ofreciendo una experiencia de integración fluida y sin complicaciones con componentes y servicios fundamentales.
Este marco de trabajo se erige como una solución integral que responde eficazmente a las necesidades de comunicación, escalabilidad, resiliencia y monitoreo, elementos clave en la construcción de aplicaciones robustas y modernas.