Spring.io has released the experimental version of Spring Native since March, a toolsuite to support the construction of compileable and executable springboot projects on GraalVM.
But first: What is GraalVM and what problem is it supposed to solve?
Background: When you mix containers and JREs.
Since the first container-based deployments became popular, the debate has arisen about whether Java as such might have its days numbered.
In my opinion, Java is too widespread and used in too many areas to be considered a single language. There's simply too much good code written in it.
On the other hand, the Java programming language, and especially the Java virtual machine, is a solution from the mid-90s that came to answer the problem of developing and running software on different platforms.
Basically, JDK lets you do that: It's a program on which your software runs. virtuallyThanks to this advancement, you could disregard the type of hardware or operating system where your software would run. You only needed to have a compatible JRE version installed on your server.
This gain, as we all know, doesn't come without a price. The Java Virtual Machine, being an additional layer between your software and your hardware, incurs a cost in memory and performance. But since there was no better solution, and because it works extremely well, these costs were accepted without question.

But now, well into the 21st century, the paradigm is changing. The container deployment model, among many other things, gives us the power to define using a layered model. un entorno de ejecución completamente virtualizado, otra forma de dar respuesta al mismo problema.

Pero, ¿Qué ocurre si mezclamos estos dos conceptos? Pues nos encontramos con lo siguiente:

Como veis, por cada servicio, se añade dentro de los contenedores una capa pesada de virtualización (JRE) sobre la propia capa de virtualización de Docker, incrementando significativa e innecesariamente el consumo de recursos.
Por todas estas razones a día de hoy en entornos de muy alta escalabilidad, no es recomendable el desarrollo de microservicios sobre una JDK “tradicional”, en detrimento de otras plataformas de ejecución relativamente más directas como node.js, python, GoLang, etc.
GraalVM: ¿Una solución al problema?

GraalVM es un entorno de ejecución de alto rendimiento para Java, JavaScript, y lenguajes basados en LLVM como C y C ++, entre otros.
Principalmente GraalVM hace de “capa traductora” entre los lenguajes de programación o el código objeto de Java y tu S.O. (o en el caso de contenedores, la siguiente capa de virtualización). Además, permite la anticipación en ejecutables nativos para acelerar el tiempo de inicio y reducir la sobrecarga de memoria.
O, dicho de otro modo, es una forma de ejecutar aplicaciones Java sin necesidad de levantar una JRE y que permite acelerara su arranque si defines una precarga de clases configurable.
Al no seguir la estructura estándar de JDK, GrallVM no ejecuta de forma directa cualquier empaquetado jar. En general, GraalVM parece tener sus propias reglas de empaquetado y configuración que daría para otra entrada.
Por suerte para nosotros, ya existen frameworks como Micronaut (https://micronaut.io/) o Quarkus (https://quarkus.io/) y experimentalmente Spring Native que nos abstraen de todo ello.
Okay, GraalVM is great, but why Spring Boot Native?
Short answer: Because of its history, its community, and everything that surrounds it.
Spring Boot, and by extension Spring, is one of the most successful Java development frameworks of all time, boasting one of the longest-standing and most active communities in the Java world. There is a wealth of accumulated expertise on Spring Boot that would be a shame to lose.
On the other hand, Micronaut is a relatively new framework (2018) that has been gaining significant traction in recent years and already boasts a strong supporting community. Its framework is considered more stable, and for deployments on GraalVM, it can be considered the preferred solution.
Also in the running is Quarkus, even more recent (March 2019). Quarkus is designed for running native Java in Kubernetes environments, following the philosophy container-first and, of course, also supported by GraalVM for this.
And although I don't rule out future posts talking about Quarkus and Micronaut, because of the years I've personally been working with Spring and the good results it has given me, Spring Boot Native had to be the first one to be examined.
Pilot construction
As a pilot project, we will set up a typical CRUD service against a PostgreSQL database, although any other database with r2dbc support would work.
We're not going to complicate things, we're going to https://start.spring.io/:

With this configuration, we click on GENERATEAnd now we have a zip file with the starting project.
Adding drivers and persistence
We open pom.xml and add the reactive drivers for PostgreSQL to the dependencies:

Since the Spring Native project is in an experimental state and Spring Data already provides a good foundation for building CRUDs, we will avoid including non-essential libraries like Apache Lombok or Hibernate JPA in this demo, as these could generate unnecessary conflicts.
Now we need to add persistence.
To do this, we used spring-data and created a Book entity, and its respective R2dbcRepository:
Libro.java

LibroRepository.java

We already have persistence, now to set up the services.
Services
We will create two Java classes: LibroServiceImpl.java (and its corresponding interface with business operations) and LibroController.java, responsible for shaping the REST facade of the CRUD.
LibroService.java

LibroServiceImpl.java

LibroController.java

We essentially have all the code copied. We just need a couple of tweaks:
- In the DemoSpringNativeApplication.java class, we added the annotation @EnableR2dbcRepositories.
- We created a PostgreSQL database and added the table for books:

- In the file application.properties, we include the connection properties to said database.

And now we have the source code ready to package.
Packing for GraalVM
La mejor forma de construir la imagen Docker con GraalVM, es utilizar el plugin spring-boot:build-image
$ mvn clean package spring-boot:build-image |
Nota: El empaquetado consume tanto una gran cantidad de memoria (oficialmente recomiendan 16GB disponibles) como una gran cantidad de tiempo (no baja de más de 5 minutos).
Una vez completado el proceso, nos debe aparecer por consola un mensaje como el siguiente:
| [INFO] Successfully built image ‘docker.io/library/demo-spring-native-graalvm:1.0.0’ |
Esto significa que, en nuestro Docker local, existe una nueva imagen con este tag que puede ser desplegada.
$ docker run -p 8080:8080 -e R2DBC_URL=… -e R2DBC_USER=… -e R2DBC_PASS=… docker.io/library/demo-spring-native-graalvm:1.0.0 |
Lo primero que os va a sorprender es el tiempo de arranque, alrededor de 0,12 segundos (Si parpadeas, te lo pierdes).
| e.s.r.d.DemoSpringNativeApplication : Started DemoSpringNativeApplication in 0.116 seconds (JVM running for 0.119) |
To test it, you can use Postman or run curl requests manually:
GET
$ curl --location --request GET 'http://localhost:8080/libros/' |
PUT (Insert new element)
$ curl --location --request PUT 'http:// localhost:8080/libros' \ |
POST (Modify an element)
$ curl --location --request POST 'http://localhost:8080/libros' \ |
DELETE
$ curl --location --request DELETE 'http://localhost:8080/libros/9780007141326' |
Comparing Consumption
Now, to be sure, we will first use the same source code to build a container with the "classic" JRE packaging and thus compare the metrics that each one yields.
Furthermore, to make the rules as fair as possible, we're simply going to assemble and launch it on a container based on a very resource-efficient image: registry.access.redhat.com/ubi8/openjdk-11-runtime.
To begin with, the startup time has gone from milliseconds to full seconds: approximately 4-5 seconds.
| e.s.r.d.DemoSpringNativeApplication: Started DemoSpringNativeApplication in 4.057 seconds (JVM running for 4.826) |
For a look at metrics more focused on resource consumption, in my case, since I have Portainer set up locally for other purposes, I'm going to limit myself to comparing graphs.
Container with openjdk-runtime:

Container with GraalVM:

As you can see, RAM usage is considerably less than half. No he apreciado diferencias de rendimiento entre ellos, aunque es cierto que al ser proyectos pequeños y además con servicios reactivos, es difícil medir diferencias sin hacer auténticas pruebas de carga.
Además, las imágenes con GraalVM pesan casi 4 veces menos (112MB vs 400MB aprox).
CONCLUSIONES
Spring Native es un inicio prometedor al que aún le faltan muchas aristas por pulir.
El proceso de construcción es delicado. En mi caso, para el ejemplo, tuve que volver a montar el proyecto base un par de veces.
No todo es compatible hasta la fecha. Por ejemplo, la propia configuración para swagger, @OpenAPIDefinition, dió problemas de arranque, aunque es posible que fuera por falta de algún detalle de la configuración del proceso de construcción.
Además, como ya he mencionado, la ejecución build-image consume mucha memoria (su documentación oficial recomienda 16GB), y tarda demasiado en completarse (~6 minutos en el caso del ejemplo). Si entramos en el contexto del desarrollo local y la integración continua, son tiempos de espera excesivos.
En resumen, Spring native es un proyecto muy en fase beta al que no debemos perderle la pista, aunque por las dificultades encontradas no lo considero lo suficientemente maduro como para utilizarlo a día de hoy profesionalmente.
Puedes descargar el fuente de este articulo accediendo a su página de GitHub
En SOLTEL tenemos una actitud innovadora hacia la tecnología porque nos apasiona. Nuestra trayectoria trabajando con distintas tecnologías a lo largo de tantos años, nos ayuda a afrontar los proyectos con muchísima perspectiva y a convertirnos en auténticos partners tecnológicos de nuestros clientes.
Articulo elaborado por Ramón Tur Vázquez, Solutions Architect en SOLTEL Group







