Desarrollando microservicios reactivos con Micronaut

Micronaut es un nuevo framework para la JVM que soporta Java, Groovy y Kotlin y que está diseñado para construir microservicios _cloud native_.

La versión actual de Micronaut permite construir un JAR con un _Hello World_ que ocupa 10MB y que se puede ejecutar con un heap de tan sólo 8MB siendo el tiempo de arranque de menos de un segundo cuando programamos con Java. Además el tiempo de arranque de Micronaut y el consumo de memoria no crece linealmente con el número de líneas de código porque toda la inyección de dependencias, AOP y generación de proxies ocurre en tiempo de compilación.

El proyecto ha sido creado por OCI y el mismo equipo que desarrolla el framework Grails. Fue oficialmente presentado por Graeme Rocher en Greach 2018 el pasado Marzo en Madrid, liberado con licencia Apache 2.0 el 23 de Mayo y publicada la primera version milestone el 30 de Mayo.

Entre los objetivos con los que se ha diseñado Micronaut se encuentran:

  • Arranque prácticamente instantáneo
  • Muy bajo consumo de memoria
  • Fácilmente testeable
  • Inyección de dependencias y AOP
  • JAR generados de muy poco tamaño
  • Prácticamente sin dependencies externas
  • 12 factor

Micronaut proporciona un servidor y cliente HTTP además de soportar microservicios _reactive_ y _non-blocking_ al estar basado en Netty. En la parte del cliente HTTP, es posible construir uno declarativamente con una simple interfaz Java y anotaciones. Este cliente es implementado, a diferencia de la mayoría de herramientas y bibliotecas existentes, en tiempo de compilatión, lo que hace que se reduzca el consumo de memoria.

Como no podía ser de otra forma, Micronaut también soporta GORM - el ORM de Grails - que proporciona un conjunto de APIs para acceder de una manera sencilla y potente a todo tipo de bases de datos SQL, así como Neo4J, Redis, MongoDB, GraphQL y Cassandra.

Micronaut está desarrollado por los creadores del framework Grails y se inspira en las lecciones aprendidas después de crear muchas aplicaciones, tanto monolitos como microservicios, durante los años utilizando Spring, Spring Boot y Grails.

Empecemos

Micronaut proporciona un CLI para poder crear aplicaciones de manera sencilla. Para instalarlo usaremos SDKMan!, del que ya hablamos hace tiempo.

$ sdk install micronaut 1.0.0.M1

Una vez instalado el cliente podemos crear una aplicación ejecutando:

$ mn create-app my-app

Por defecto Micronaut crea una aplicación Java y utiliza Gradle como sistema de build pero es muy sencillo, por ejemplo crear una aplicación Kotlin y usar Maven:

mn create-app -build maven -features kotlin my-app

Además, si ejecutamos únicamente mn entraremos en el modo interactivo en el que tenemos autocompletado usando el tabulador y podremos ver todas las opciones y comando existentes:

 mn
| Starting interactive mode...
| Enter a command name to run. Use TAB for completion:
mn> 

create-app   create-federation   create-function   create-profile   help   list-profiles   profile-info

mn> create-app -

-inplace     -build       -lang        -profile     -features    

mn> list-profiles 
| Available Profiles
--------------------
* base - The base profile
* federation - The federation profile
* function - The function profile
* function-aws - The function profile for AWS Lambda
* service - The service profile


mn> profile-info base
Profile: base
--------------------
The base profile

Provided Commands:
--------------------
* create-bean - Creates a singleton bean
* create-job - Creates a job with scheduled method
* help - Prints help information for a specific command

Provided Features:
--------------------
* annotation-api - Adds Java annotation API
* config-consul - Adds support for Distributed Configuration with Consul (https://www.consul.io)
* discovery-consul - Adds support for Service Discovery with Consul (https://www.consul.io)
* discovery-eureka - Adds support for Service Discovery with Eureka
* groovy - Creates a Groovy application
* hibernate-gorm - Adds support for GORM persistence framework
* hibernate-jpa - Adds support for Hibernate/JPA
* http-client - Adds support for creating HTTP clients
* http-server - Adds support for running a Netty server
* java - Creates a Java application
* jdbc-dbcp - Configures SQL DataSource instances using Commons DBCP
* jdbc-hikari - Configures SQL DataSource instances using Hikari Connection Pool
* jdbc-tomcat - Configures SQL DataSource instances using Tomcat Connection Pool
* junit - Adds support for the JUnit testing framework
* kotlin - Creates a Kotlin application
* mongo-gorm - Configures GORM for MongoDB for Groovy applications
* mongo-reactive - Adds support for the Mongo Reactive Streams Driver
* neo4j-bolt - Adds support for the Neo4j Bolt Driver
* neo4j-gorm - Configures GORM for Neo4j for Groovy applications
* redis-lettuce - Configures the Lettuce driver for Redis
* security-jwt - Adds support for JWT (JSON Web Token) based Authentication
* security-session - Adds support for Session based Authentication
* spek - Adds support for the Spek testing framewokr
* spock - Adds support for the Spock testing framework
* tracing-jaeger - Adds support for distributed tracing with Jaeger (https://www.jaegertracing.io)
* tracing-zipkin - Adds support for distributed tracing with Zipkin (https://zipkin.io)

Nuestro primer servicio

El siguiente código muestra cómo crear un endpoint HTTP en Micronaut:

@Controller("/hello")
public class HelloController {
    @Get("/{name}")
    public String hello(String name) {
        return "Hello " + name;
    }
}

Como se puede ver el modelo de programación es muy similar a Spring Boot o Grails. Los desarrolladores que estén acostumbrados a ambos frameworks van a tener una curva de aprendizaje muy baja. Esto permitirá, por ejemplo una transición rápida y sencilla a Micronaut.

Si arrancamos la aplicación y nos conectamos con un navegador (por defecto el servidor HTTP se ejecuta en un puerto aleatorio pero es posible definir uno por configuración) veremos algo como esto:

$ ./gradlew run
> Task :run 
23:59:44.420 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 502ms. Server Running: http://localhost:43134

¿Y esto cómo lo testeamos?

Para ello vamos a utilizar Spock aunque es posible utilizar JUnit o Spek si así lo queremos.

En primer lugar vamos a crear un interfaz que nos permitirá comunicarnos con el servidor. Micronaut será el encargado de, en tiempo de compilación, implementar el interfaz con el cliente HTTP por nosotros.

@Client('/hello')
interface HelloClient {
    @Get("/{name}")
    String hello(String name)
}

Ahora ya podemos crear el test:

class HelloControllerSpec extends Specification {

    @Shared @AutoCleanup
    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) // 1

    void "test hello world"() {
        given:
            HelloClient client = embeddedServer.applicationContext.getBean(HelloClient) // 2

        expect:
            client.hello("Iván") == "Hello Iván" // 3
    }
}
  1. Creamos una instancia única y compartida por todos los tests de un servidor embebido que ejecuta nuestra aplicación.
  2. Obtenemos el _bean_ del interfaz _HelloClient_ que hemos definido anteriormente y que utilizaremos como cliente HTTP para conectarnos al servidor. Recordemos que Micronaut ha implementado el cliente HTTP.
  3. Ejecutamos la llamada HTTP y comprobamos el resultado.

Una de las premisas con las que se ha construido Micronaut es que sea muy rápido y sencillo de testear. Cuando utilizamos frameworks como Spring y Grails, escribimos principalmente test unitarios en lugar de tests funcionales o de integración no porque prefiramos hacerlo ni porque sea deseable, sino porque los tests funcionales son muy lentos. Tendemos a _mockear_ nuestra infraestructura, dependencias,... simplemente porque arrancar el servidor es lento y pesado. En Micronaut todo ocurre en tiempo de compilación así que la velocidad de ejecución de los tests es muy rápida.

Como podemos ver la ejecución del test es de sólo 185 ms. Esto no es ningún truco. Es la aplicación real comunicandose utilizando HTTP con el servidor. Para comprobarlo podemos incrementar el nivel del log del cliente HTTP añadiendo al archivo logback.xml:

<logger name="io.micronaut.http.client" level="TRACE">

Si ejecutamos el test de nuevo podemos comprobar que se hacen peticiones HTTP reales entre el cliente y el servidor.

Programación reactiva

Micronaut también soporta programación reactiva _out-of-the-box_ al implementar Reactive Streams por lo que es posible utilizar cualquier biblioteca que lo soporte como RxJava2, Reactor o Akka.

Vamos a añadir soporte RxJava2 al ejemplo anterior:

@Get("/{name}")
public Single<String> hello(String name) { // 1
    return Single.just("Hello " + name);  // 2
}
  1. En lugar de devolver un String devolvemos un Single<String>. La clase Single de RxJava2 implementa el Patrón Reactivo para un único valor de respuesta.
  2. Creamos un Single a partir del mensaje con la respuesta.

El cliente quedaría:

@Client('/hello')
interface HelloClient {
    @Get("/{name}")
    Single<String> hello(String name)
}

Y finalmente el test:

void "test hello world"() {
    given:
        HelloClient client = embeddedServer.applicationContext.getBean(HelloClient)

    expect:
        client.hello("Iván").blockingGet() == "Hello Iván" // 1
}
  1. En el test convertimos la llamada en bloqueante

Con un pequeño cambio tenemos una implementación reactive y non-blocking en el servidor y un cliente reactive para testear todo.

Micronaut es más que esto

Hemos visto un ejemplo muy sencillo pero Micronaut ofrece gran cantidad de opciones y características pensadas en Microservicios:

  • Service discovery: Con soporte de Consul y Eureka
  • Circuit Breaker con soporte de Retry
  • Configuración compartida
  • Cliente HTTP con Load-Balancing
  • Tracing con soporte para Zipkin y Jaeger
  • Seguridad
  • Soporte de serverless en AWS Lambda
  • Soporte para Hystrix y Ribbon
  • Micrometer
  • Y muchas más integraciones

Otra característica a destacar es que estas integraciones están construidas sin añadir práctiacemente dependencias adicionales al framework lo que evita que los JARs finales tenga excesivo tamaño.

Más información

Esto es sólo el comienzo. Podeis encontrar el código completo de la apliación hello-world en Github.

Para conocer más sobre el framework recomiendo la de presentación de Graeme: _Launching the Micro Future: Groovy, Grails and Project Particle_ disponible en el canal de Youtube de Greach.

También está disponible la documentación oficial, la documentación del API y guías con distintos ejemplos en dónde se irán añadiendo nuevas en los próximos meses.

Finalmente la versión _Milestone 2_ estará disponible a final de Junio y se espera la versión final 1.0 antes de fin de año.

Ver todos los comentarios en https://www.genbeta.com

VER 0 Comentario

Portada de Genbeta