Imágenes nativas de GraalVM con Spring Native

Imagenes Nativas de Graal VM con Spring Native

Imagenes Nativas de Graal VM con Spring Native


En este este artículo, vamos a ver una introducción a las imágenes nativas de GraalVM con Spring Native, esta es una gran mejora dentro de Spring que va a permitir optimizar y mejorar el rendimiento en el arranque de nuestras aplicaciones.

Spring Native, que actualmente se encuentra como beta release. Nos permitirá convertir nuestros proyectos Spring en ejecutables llamados imágenes nativas. Lo que nos permitirá una mejora considerable en el arranque y una menor sobrecarga de memoria en tiempo de ejecución si lo comparamos con la JVM.

Para poder hacer uso de estas mejoras se va a apoyar en GraalVM, la cual es un Java VM y JDK que están basadas en HotSpot/OpenJDK.

Esta es una mejora que se lleva bastante tiempo esperando ya que las aplicaciones cloud native tienen cada vez un mayor impacto. Por lo que los tiempos de arranque o de escalado deberían ser mucho mejores en nuestras aplicaciones Spring. Y si además le añadimos que otros framework como por ejemplo Quarkus, que funciona bajo GraalVM y HotSpot, y se orienta perfectamente al cloud, con más motivo.

Esta primera versión fue lanzada el 11 de Marzo y se encuentra disponible en start.spring.io.

Características principales de GraalVM

GraalVM nos proporciona muchas y diferentes características. Entre las cuales nos ofrece un componente que es conocido como Substrate VM que nos permitirá compilar códigos de bytes con AOT (Java Ahead of Time) en un ejecutable nativo.

Esta mejora nos va a permitir mejorar en gran medida, el rendimiento de cualquier aplicación en Spring Boot. Ya que en tiempo de ejecución realiza bastantes operaciones, como leer del claspath ficheros y realiza bastantes operaciones por reflexión.

Para poder hacer frente a las limitaciones comentadas arriba, lo que se hace es registrar todas las interacciones con la aplicación que se ejecutan en una JVM. Esto se va a hacer a través de un agente Java proporcionado por Graal VM. Una vez finaliza la ejecución se descargan todas las interacciones en ficheros de configuración.


La forma habitual de hacer frente a esta limitación es registrar todas las interacciones con la aplicación que se ejecuta en una JVM a través de un agente Java proporcionado por Graal VM. Al final de la ejecución, el agente descarga todas las interacciones registradas en archivos de configuración dedicados.

Ejemplo con Spring Native y Base de Datos

A continuación vamos a jugar un poco con Spring Native viendo un pequeño ejemplo y veremos el potencial que este nuevo módulo podrá llegar a tener.

Este ejemplo que vamos a realizar será con Cloud Native Buildpacks.  De manera que podemos crear imágenes Docker compatibles que pueden ejecutarse en cualquier sitio, esta funcionalidad nos la da Spring Boot através de Maven.

Si has tenido la oportunidad de realizar algún ejemplo o trabajar con Quarkus, has podido ver sus tiempos de arranque y de consumo de memoria, que para una aplicación normal son increíblemente rápidos. Esa es la idea detrás de este módulo intentar reducir esos tiempos de arranque y de consumo de memoria. Pero lo mejor sin duda es verlo en acción.

La aplicación en la que hemos desarrollado este ejemplo esta realizada con Java 11 de una manera totalmente reactiva y con una base de datos en memoria como H2. Vamos a ver las partes de esta aplicación:

Dependencias Spring Native en Maven

Lo primero que vamos a hacer es añadir las dependencias maven necesarias para añadir este módulo de Spring. Por un lado vamos a añadir el plugin de spring-aot-maven-plugin, y por otro lado la dependencia spring-native.

 <plugin>
        <artifactId>spring-aot-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
            <id>generate</id>
          </execution>
        </executions>
        <groupId>org.springframework.experimental</groupId>
        <version>0.9.1</version>
     </plugin>
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
            <builder>paketobuildpacks/builder:tiny</builder>
            <env>
                <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
            </env>
        </image>
    </configuration>
</plugin>
    <dependency>
      <artifactId>spring-native</artifactId>
      <groupId>org.springframework.experimental</groupId>
      <version>0.9.1</version>
    </dependency>

Spring Native 0.9.1 solo soporta Spring Boot Starter Parent 2.4.4

Crear aplicación reactiva

Como hemos comentado anteriormente, la aplicación desarrollada para probar spring native, es totalmente reactiva. Por lo que hemos hecho uso de webflux de spring y de spring data reactivo, veamos un poco más la implementeación:

Nuestra API será totalmente reactiva haciendo uso de WebFlux. Por lo que por un lado creamos las rutas y el handler encargado de esas rutas; y por otro lado haremos uso de la librería spring-data-r2dbc, para que nuestra conexión a base de datos sea también reactiva:

@Configuration
public class CarRoutes {


  @Bean
  public RouterFunction<ServerResponse> routes(CarHandler handler) {
    return route().path(
        "/car", builder -> builder
            .GET("/", handler::getAll)
            .GET("/{id}", handler::getOne)
    ).build();
  }
}




@RequiredArgsConstructor
public class CarHandler {

  private final CarService service;

  public Mono<ServerResponse> getAll(ServerRequest req) {
    var all = service.findAll(Sort.by("model", "brand"));
    return ok().body(BodyInserters.fromPublisher(all, Car.class));
  }

  public Mono<ServerResponse> getOne(ServerRequest req) {
    var mono = service
        .findById(Long.valueOf(req.pathVariable("id")))
        .switchIfEmpty(Mono.error(() -> new ResponseStatusException(NOT_FOUND)));
    return ok().body(fromPublisher(mono, Car.class));
  }
}

En las dos clases anteriores, hemos creado por un lado los endpoint y por otro lado un handler que es el encargado de realizar la lógica del endpoint.

Y a continuación creamos el repositorio para que sea totalmente reactivo, haciendo uso de ReactiveSortingRepository.

public interface CarRepository extends ReactiveSortingRepository<Car, Long> {

}

Una vez hemos creamos y vemos el correcto funcionamiento de la aplicación, llega el momento de generar la imagen:

mvn spring-boot:build-image

La compilación de ATO lleva algo de tiempo, y una vez realizado podemos ejecutar la imagen de la siguiente manera:

docker run -it --rm -p8080:8080 docker.io/library/spring-native-example:1.0-SNAPSHOT     

Al intentar arrancar la aplicación nos encontraremos una serie de problemas:

  • ConnectionFactoryConfigurations: Dará un error en el que dirá que no puede realizar la configuración del connectionFactory. Parece que Spring Native no ha incorporado alguna autoconfiguración y lo añadiremos vía resources en el META-INF:
[
{
  "name":"org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryConfigurations$PooledConnectionFactoryCondition",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"java.time.LocalDate"
},
{
  "name":"java.lang.Iterable",
  "allDeclaredFields":true,
  "allDeclaredMethods":true,
  "allPublicMethods":true
},
{
  "name":"com.refactorizando.example.springnative.Person",
  "allDeclaredFields":true,
  "allDeclaredMethods":true,
  "allPublicMethods":true,
  "allDeclaredConstructors":true
},
{
  "name":"org.springframework.data.domain.Sort"
}
]
  • Proxies: Dará algún error de proxy debido a que cuando se utiliza Spring Data, este hace proxy de la interfaz repository de manera automática. Pero con GraalVM hay que configurarla de la siguiente manera en el META-INF:
[
  ["com.refactorizando.example.springnative.CarRepository","org.springframework.data.repository.Repository","org.springframework.transaction.interceptor.TransactionalProxy","org.springframework.aop.framework.Advised","org.springframework.core.DecoratingProxy"]
]
  • Serialización: Con GraalVM, necesitamos añadir también la configuración de la serialización en el META-INF, para nuestra clase Car:
[
{
  "name":"com.refactorizando.example.springnative.Car"
},
{
  "name":"java.time.LocalDate"
},
{
  "name":"java.lang.String"
},
{
  "name":"java.time.Ser"
}
]

Una vez hemos terminado con las configuraciones. Volvemos a configurar la imágen como hemos indicado antes y arrancamos con docker. Podemos probra con el siguiente endpoint y veremos que nos devuelve información:

curl http://localhost:8081/car/1

Conclusión

Aunque todavía Spring Native no se encuentra en su versión definitiva, y hay que realizar, como hemos visto alguna configuración a mano. Ya podemos ir probando de lo que será capaz y de las funcionalidades que nos ofrece. Además es sin duda una de las mejoras más esperadas de Spring en los últimos tiempos.

En esta entrada sobre imágenes nativas de GraalVM con Spring Native, hemos podido ver como Spring ha facilitado las configuraciones para poder realizar imágenes nativas de una manera fácil y sencilla.

Si quieres puedes echar un vistazo al ejemplo creado con Spring Native y conexión a base de datos, en nuestro github.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com y te ayudaremos encantados!


No te olvides de seguirnos en nuestras redes sociales Facebook o Twitter para estar actualizado.


Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *