Hablemos sobre .Net y Azure

Etiqueta: Azure Functions

Gestión de configuraciones en Azure Functions

La semana pasada escribí un artículo sobre como inyectar dependencias en Azure Functions, debido a que la plantilla que utilizamos cuando creamos un proyecto nuevo no nos provee los mecanismos necesarios.

Ahora veremos como manejar configuraciones, de forma tal que podamos utilizar el archivo local.settingns.json, variables de entorno y/o User Secrets.

Antes de comenzar

El framework de Azure Functions está preparado para leer las configuraciones que incluimos mediante el portal de Azure en la sección de Configuraciones.

Tomaremos el el concepto de Startup del artículo sobre inyección de dependencias que mencioné anteriormente. Además tomaremos el código base y construiremos sobre él.

Contexto

Si bien cuando desplegamos nuestro código al servicio de Azure Functions las configuraciones configuradas desde el portal se leen automáticamente, puede que necesitemos personalizar el mecanismo con el cual esas configuraciones con leídas.

Por ejemplo, en nuestro ambiente local, usar User Secrets es recomendado. En ambientes pre productivos, podríamos necesitar leer un archivo adicional, como puede ser stage.settings.json.

Requerimiento

El requerimiento de hoy es bastante simple, necesitamos poder leer configuraciones utilizando User Secrets, el archivo local.settings.json y variables de entorno.

Implementación

Cuando creamos el Startup y heredamos de la clase FunctionsStartup, tendremos disponible un método virtual llamado ConfigureAppConfiguration. Es este método es que nos permitirá establecer como son leídas las configuraciones.

public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
    }

    public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
    {
        var context = builder.GetContext();

        builder.ConfigurationBuilder
            .SetBasePath(context.ApplicationRootPath)
            .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
            .AddUserSecrets(Assembly.GetExecutingAssembly(), optional: true)
            .AddEnvironmentVariables();
    }
}

SetBasePath

Siempre que sea necesario leer archivos locales, necesitamos establecer el directorio base. En aplicaciones web o APIs solemos utilizar el directorio actual, .SetBasePath(Directory.GetCurrentDirectory()).

En el caso de Azure Functions necesitamos utilizar el contexto de ejecución, builder.GetContext().ApplicationRootPath.

AddUserSecrets

Cuando instalamos la librería Microsoft.Extensions.Configuration.UserSecrets tenemos que instalar la versión 3.1.XX. Por defecto, el Gestor de Paquetes (Package Manager) instalará la ultima versión, que a la fecha actual es 5.0.0, sin embargo el framework todavía no fue actualizado y sigue utilizando la versión de .Net Core 3.1.

Es inminente la actualización del framework de Azure Functions a la version .Net 5, pero al momento de escribir este artículo, todavía utiliza la versión 3.1.

Conclusión

En este breve post vimos cómo podemos configurar nuestra Azure Function para utilizar los distintos tipos de mecanismos para manejo de configuraciones utilizando todas las herramientas que le framework nos provee.

Con muy poco código, tenemos disponible variables de entorno, User Secrets y archivos json.

Inyección de dependencias en Azure Functions

A estas alturas, inyección de dependencias es uno de esos patrones de diseño que se ha convertido casi obligatorio, no sólo porque nos ayuda a escribir código mas testeable, sino también a escribir componentes desacoplados. No importa que tan compleja o simple sea una clase, si tiene alguna dependencia la inyectaremos mediante el constructor.

En este post veremos los pasos necesarios para poder comenzar a utilizar inyección de dependencias de la misma forma que lo hacemos en cualquier aplicación web.

Contexto

Cuando iniciamos un proyecto nuevo de Azure Functions, a diferencia de como sucede con proyectos de ASP.Net, no disponemos de ningún mecanismo listo para usar para inyectar dependencias.

A modo de ejemplo, pensemos una Azure Function llamada “Greeting” del tipo HttpTrigger que en base a un nombre devuelve un saludo.

Como parte de nuestra solución, implementamos un servicio muy simple de forma que nuestro código sea testeable.

public class GreetingService : IGreetingService
{
    public string GetGreeting(string name) =>
        string.IsNullOrEmpty(name)
            ? "Hi there!"
            : $"Hi {name}!";
}

Luego, implementamos el código de la función de forma tal que lea el nombre recibido como query string y obtenga el saludo correspondiente utilizando el servicio anterior.

public static class Greeting
{
    [FunctionName("Greeting")]
    public static IActionResult Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req)
    {
        var name = req.Query["name"];
        var greetingService = new GreetingService();

        var greeting = greetingService.GetGreeting(name);

        return new OkObjectResult(greeting);
    }
}

En la línea 8 podemos apreciar el problema: para poder utilizar el servicio necesitamos crear una instancia del mismo.

Requerimiento

Necesitamos implementar Inyección de Dependencias, no sólo porque las buenas prácticas así lo recomiendan, sino también porque queremos escribir Test Unitarios, y sabemos que de la forma que está escrito nuestro código es imposible.

Parte del requerimiento consiste en utilizar todas las herramientas que le framework nos provea, de forma que no tengamos que implementar nuestro propio set de clases o Contenedor para inyección de dependencias.

Implementación

Para poder completar nuestro requerimiento necesitamos dos cosas:

  1. Registrar y configurar nuestros servicios (generalmente cuando se inicia la aplicación)
  2. Inyectar las dependencias en forma automática, como lo hacemos en aplicaciones Web o APIs con ASP.Net.

Por suerte, el framework de Azure Functions nos provee de ambos mecanismos, solo tenemos que realizar las configuraciones necesarias.

Librerías

Comenzaremos instalando la librería Microsoft.Azure.Functions.Extensions, la cuál nos va facilitar los componentes que necesitamos.

dotnet add package Microsoft.Azure.Functions.Extensions

Startup

Azure Functions dispone de una clase abstracta diseñada para ser ejecutada al iniciar la aplicación. Esta clase se llama FunctionsStartup y nos va a permitir realizar la registración de servicios que necesitamos.

Comenzaremos agregando una nueva clase al proyecto llamada Startup y haremos que herede de la clase FunctionsStartup.

using Demo;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(Startup))]

namespace Demo
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
        }
    }
}

El atributo de ensamblado (línea 4) es fundamental para que el framework pueda detectar los startups que deben ser invocados.

Configure

Al heredar de la clase abstracta FunctionsStartup, tendremos que sobrescribir el método Configure. Aquí haremos la registración de los servicios necesarios, igual que lo hacemos en cualquier aplicación ASP.Net. Siguiendo con el ejemplo, registraremos la clase GreetingService.

public override void Configure(IFunctionsHostBuilder builder)
{
    builder.Services.AddSingleton<IGreetingService, GreetingService>();
}

Function

El último paso que nos queda es modificar la función para poder inyectar los servicios necesarios como lo haríamos con cualquier otra clase. Las modificaciones son:

  1. Modificar la clase y el método Run para que ya no sean static.
  2. Agregar un constructor parametrizado con los servicios que queremos recibir.
  3. Guardar los servicios en variables de instancia.
  4. Modificar el metodo en cuestion para utilizar las variables de instancia
public class Greeting
{
    private readonly IGreetingService _greetingService;

    public Greeting(IGreetingService greetingService) =>
        _greetingService = greetingService;

    [FunctionName("Greeting")]
    public IActionResult Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req)
    {
        var name = req.Query["name"];
        var greeting = _greetingService.GetGreeting(name);

        return new OkObjectResult(greeting);
    }
}

Conclusión

En este breve post vimos cómo podemos configurar nuestra Azure Function para utilizar Inyección de Dependencias utilizando todas las herramientas que le framework nos provee.

Si querés acceder al código completo, te dejo a continuación el repositorio en Github.

Azure Functions en la vida real

Cuándo hablamos de serverless y orientación a eventos, Azure Functions es uno de los primeros servicios en los que se suele pensar.

Este potente servicio, diseñado para escalar sin límites, nos ofrece un sin fin de posibilidades a un costo realmente muy bajo.

Sin embargo, si todavía no tenemos experiencia en el mundo serverless, puede resultar confuso encontrar casos de uso simples donde poder hacer uso.

En esta sesión te cuento cómo podemos comenzar a sacar provecho de Azure Functions con cuatro casos de uso simples y útiles.

Azure Functions en la vida real

Para ver el código completo de los ejemplos, te dejo el repo de github aquí.

© 2021 Facu The Rock

Tema por Anders NorenArriba ↑