Introducción a Spring Cloud Function en Azure

Este artículo le explica cómo usar Spring Cloud Function para desarrollar una función de Java y publicarla en Azure Functions. Cuando haya terminado, el código de la función se ejecuta en el Plan de consumo en Azure y puede activarse mediante una solicitud HTTP.

Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.

Requisitos previos

Para desarrollar funciones con Java, debe tener instalado lo siguiente:

Importante

Debe establecer la variable de entorno JAVA_HOME en la ubicación de instalación del JDK para completar esta guía de inicio rápido.

Qué vamos a crear

Vamos a crear una función clásica "Hola mundo" que se ejecuta en Azure Functions y se configura con Spring Cloud Function.

Recibirá un objeto JSON User sencillo, que contiene un nombre de usuario, y devolverá un objeto Greeting, que contiene el mensaje de bienvenida para ese usuario.

El proyecto que vamos a compilar está disponible en el repositorio hello-spring-function-azure de GitHub. Puede usar directamente el repositorio de ejemplo si quiere ver el trabajo final que se describe en esta guía de inicio rápido.

Creación de un nuevo proyecto de Maven

Vamos a crear un proyecto de Maven vacío y a configurarlo con Spring Cloud Function y Azure Functions.

En una carpeta vacía, cree un nuevo archivo pom.xml y copie y pegue el contenido del proyecto de ejemplo en el archivo pom.xml.

Nota

Este archivo usa las dependencias de Maven tanto de Spring Boot como de Spring Cloud Function, y configura los complementos de Maven para Spring Boot y Azure Functions.

Deberá personalizar algunas propiedades de la aplicación:

  • <functionAppName> es el nombre de la función de Azure
  • <functionAppRegion> es el nombre de la región de Azure donde se implementa la función.
  • <functionResourceGroup> es el nombre del grupo de recursos de Azure que se usa.

Cambie esas propiedades directamente cerca de la parte superior del archivo pom.xml, como se muestra en el ejemplo siguiente:

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.source>11</maven.compiler.source>
  <maven.compiler.target>11</maven.compiler.target>

  <azure.functions.java.library.version>1.4.2</azure.functions.java.library.version>
  <azure.functions.maven.plugin.version>1.13.0</azure.functions.maven.plugin.version>

  <!-- customize those two properties. The functionAppName should be unique across Azure -->
  <functionResourceGroup>my-spring-function-resource-group</functionResourceGroup>
  <functionAppName>my-spring-function</functionAppName>

  <functionAppRegion>westeurope</functionAppRegion>
  <stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
  <start-class>com.example.DemoApplication</start-class>
</properties>

Creación de archivos de configuración de Azure

Cree una carpeta src/main/azure y agregue los siguientes archivos de configuración de Azure Functions.

host.json:

{
  "version": "2.0",
  "functionTimeout": "00:10:00"
}

local.settings.json:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "java",
    "MAIN_CLASS":"com.example.DemoApplication",
    "AzureWebJobsDashboard": ""
  }
}

Creación de objetos de dominio

Azure Functions puede recibir y enviar objetos en formato JSON. Ahora vamos a crear los objetos User y Greeting, que representan nuestro modelo de dominio. Puede crear objetos más complejos, con más propiedades, si desea personalizar este inicio rápido y hacerlo más interesante para su caso.

Cree una carpetasrc/main/java/com/example/model y agregue los dos archivos siguientes:

User.java:

package com.example.model;

public class User {

    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Greeting.java:

package com.example.model;

public class Greeting {

    private String message;

    public Greeting() {
    }

    public Greeting(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

Creación de una aplicación de Spring Boot

Esta aplicación administrará toda la lógica de negocios y tendrá acceso a todo el ecosistema de Spring boot. Esta funcionalidad ofrece dos ventajas principales en comparación con una función estándar de Azure Functions:

  • No depende de las API de Azure Functions, por lo que se puede migrar fácilmente a otros sistemas. Por ejemplo, podría reutilizarse en una aplicación de Spring Boot normal.
  • Puede usar todas las anotaciones @Enable de Spring Boot para agregar nuevas características.

En la carpeta src/main/java/com/example, cree el siguiente archivo, que es una aplicación de Spring Boot normal:

DemoApplication.java:

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Ahora cree el siguiente archivo, que contiene un componente de Spring Boot que representa la función que deseamos ejecutar:

Hello.java:

package com.example;

import com.example.model.Greeting;
import com.example.model.User;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.function.Function;

@Component
public class Hello implements Function<Mono<User>, Mono<Greeting>> {

    public Mono<Greeting> apply(Mono<User> mono) {
        return mono.map(user -> new Greeting("Hello, " + user.getName() + "!\n"));
    }
}

Nota

La función Hello es bastante específica:

  • Es una java.util.function.Function. Contiene la lógica de negocios y usa una API de Java estándar para transformar un objeto en otro.
  • Como tiene la anotación @Component, es un bean de Spring y, de manera predeterminada, su nombre es el de la clase pero empezando con minúscula: hello. Seguir esta convención de nomenclatura es importante si desea crear otras funciones en la aplicación. El nombre debe coincidir con el nombre en Azure Functions que crearemos en la sección siguiente.

Creación de la función de Azure

Para aprovechar las ventajas de la API de Azure Functions, vamos a codificar una función de Azure Functions que delegará su ejecución en la función de Spring Cloud Function que hemos creado en el paso anterior.

En la carpeta src/main/java/com/example, cree el siguiente archivo de clase de la función de Azure:

HelloHandler.java:

package com.example;

import com.example.model.Greeting;
import com.example.model.User;
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;

import java.util.Optional;

public class HelloHandler extends FunctionInvoker<User, Greeting> {

    @FunctionName("hello")
    public HttpResponseMessage execute(
        @HttpTrigger(name = "request", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<User>> request,
        ExecutionContext context) {
        User user = request.getBody()
                           .filter((u -> u.getName() != null))
                           .orElseGet(() -> new User(
                               request.getQueryParameters()
                                      .getOrDefault("name", "world")));
        context.getLogger().info("Greeting user name: " + user.getName());
        return request
            .createResponseBuilder(HttpStatus.OK)
            .body(handleRequest(user, context))
            .header("Content-Type", "application/json")
            .build();
    }
}

Esta clase de Java es una función de Azure, con las siguientes características interesantes:

  • Extiende la clase FunctionInvoker, que crea el vínculo entre Azure Functions y Spring Cloud Functions. FunctionInvoker proporciona el método handleRequest() que se usa en el método body().
  • El nombre de la función, tal como se define en la anotación @FunctionName("hello"), es hello.
  • Es una función real de Azure, por lo que puede usar toda la API de Azure Functions aquí.

Adición de pruebas unitarias

Este paso es opcional, pero se recomienda validar que la aplicación funciona correctamente.

Cree la carpetasrc/test/java/com/example y agregue las siguientes pruebas de JUnit:

HelloTest.java:

package com.example;

import com.example.model.Greeting;
import com.example.model.User;
import com.microsoft.azure.functions.ExecutionContext;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import reactor.core.publisher.Mono;

import java.util.logging.Logger;

import static org.assertj.core.api.Assertions.assertThat;

public class HelloTest {

    @Test
    public void test() {
        Mono<Greeting> result = new Hello().apply(Mono.just(new User("foo")));
        assertThat(result.block().getMessage()).isEqualTo("Hello, foo!\n");
    }

    @Test
    public void start() {
        FunctionInvoker<User, Greeting> handler = new FunctionInvoker<>(
            Hello.class);
        Greeting result = handler.handleRequest(new User("foo"), new ExecutionContext() {
            @Override
            public Logger getLogger() {
                return Logger.getLogger(HelloTest.class.getName());
            }

            @Override
            public String getInvocationId() {
                return "id1";
            }

            @Override
            public String getFunctionName() {
                return "hello";
            }
        });
        handler.close();
        assertThat(result.getMessage()).isEqualTo("Hello, foo!\n");
    }
}

Ahora puede probar la función de Azure con Maven:

mvn clean test

Ejecución de la función de forma local

Antes de implementar la aplicación en Azure Functions, vamos a probarla de forma local.

Primero debe empaquetar la aplicación en un archivo Jar:

mvn package

Ahora que la aplicación está empaquetada, puede ejecutarla mediante con el complemento azure-functions de Maven:

mvn azure-functions:run

La función de Azure estará disponible en el host local, a través del puerto 7071. Para probar la función, envíe una solicitud POST con un objeto User en formato JSON. Por ejemplo, con cURL:

curl -X POST http://localhost:7071/api/hello -d "{\"name\":\"Azure\"}"

La función responderá con un objeto Greeting, aún en formato JSON:

{
  "message": "Hello, Azure!\n"
}

Esta es una captura de pantalla de la solicitud cURL en la parte superior de la pantalla y la función local de Azure en la parte inferior:

Función de Azure ejecutándose localmente

Depuración local de la función

En las secciones siguientes, se explica cómo depurar la función.

Depuración mediante Intellij IDEA

Abra el proyecto en Intellij IDEA y, a continuación, cree la configuración de ejecución Depuración remota de JVM para adjuntarla. Para más información, consulte Tutorial: Depuración remota.

Creación de una configuración de ejecución de depuración remota de JVM

Ejecute la aplicación con el comando siguiente:

mvn azure-functions:run -DenableDebug

Cuando se inicie la aplicación, verá la siguiente salida:

Worker process started and initialized.
Listening for transport dt_socket at address: 5005

Inicie la depuración del proyecto en Intellij IDEA. Verá la salida siguiente:

Connected to the target VM, address: 'localhost:5005', transport: 'socket'

Marque los puntos de interrupción que desea depurar. Después de enviar una solicitud, Intellij IDEA entrará en modo de depuración.

Depuración mediante Visual Studio Code

Abra el proyecto en Visual Studio Code y, a continuación, configure el contenido siguiente en el archivo launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "java",
            "name": "Attach to Remote Program",
            "request": "attach",
            "hostName": "127.0.0.1",
            "port": 5005
        }
    ]
}

Ejecute la aplicación con el comando siguiente:

mvn azure-functions:run -DenableDebug

Cuando se inicie la aplicación, verá la siguiente salida:

Worker process started and initialized.
Listening for transport dt_socket at address: 5005

Inicie la depuración del proyecto en Visual Studio Code y, a continuación, marque los puntos de interrupción que desea depurar. Después de enviar una solicitud, Visual Studio Code entrará en modo de depuración. Para más información, consulte Ejecución y depuración en Java.

Implementación de la función en Azure Functions

Ahora, va a publicar la función de Azure en el entorno de producción. Recuerde que las propiedades , y que ha definido en el archivopom.xmlse usarán <functionAppName><functionAppRegion> para configurar la <functionResourceGroup> función. <functionAppName>

Nota

El complemento de Maven se debe autenticar en Azure. Si ha instalado la CLI de Azure, use az login antes de continuar. Para más opciones de autenticación, consulte Autenticación en el repositorio azure-maven-plugins.

Ejecute Maven para implementar la función automáticamente:

mvn azure-functions:deploy

Ahora, vaya al Azure Portal para buscar el que se ha creado.

Seleccione la función:

  • En la información general de la función, anote la dirección URL de la función.
  • Seleccione la pestaña Características de la plataforma para buscar el servicio Secuencias de registro y seleccione este servicio para comprobar la función en ejecución.

Ahora, igual que hizo en la sección anterior, use cURL para acceder a la función en ejecución, como se muestra en el ejemplo siguiente. Asegúrese de reemplazar your-function-name por el nombre real de la función.

curl https://your-function-name.azurewebsites.net/api/hello -d "{\"name\":\"Azure\"}"

Igual que en la sección anterior, la función responderá con un objeto Greeting, aún en formato JSON:

{
  "message": "Hello, Azure!\n"
}

Enhorabuena, ya tiene una función de Spring Cloud Function en ejecución en Azure Functions.

Pasos siguientes

Para más información acerca de Spring y Azure, vaya al centro de documentación de Azure.