Una introducción a Vert.x, el framework Java más rápido en la actualidad

Si recientemente buscó en Google "el mejor marco web", es posible que se haya encontrado con los puntos de referencia de Techempower, donde se clasifican más de trescientos marcos. Allí es posible que haya notado que Vert.x es uno de los mejores clasificados, si no el primero según algunas medidas.

Así que hablemos de ello.

Vert.x es un marco web políglota que comparte funcionalidades comunes entre sus lenguajes compatibles Java, Kotlin, Scala, Ruby y Javascript. Independientemente del idioma, Vert.x opera en la máquina virtual Java (JVM). Al ser modular y liviano, está orientado al desarrollo de microservicios.

Los puntos de referencia de Techempower miden el rendimiento de la actualización, obtención y entrega de datos de una base de datos. Cuantas más solicitudes se atiendan por segundo, mejor. En un escenario de E / S de este tipo en el que hay poca informática involucrada, cualquier marco sin bloqueo tendría una ventaja. En los últimos años, este paradigma es casi inseparable de Node.js, que lo popularizó con su ciclo de eventos de un solo subproceso.

Vert.x, como Node, opera un solo bucle de eventos. Pero Vert.x también aprovecha la JVM. Mientras que Node se ejecuta en un solo núcleo, Vert.x mantiene un grupo de subprocesos con un tamaño que puede coincidir con el número de núcleos disponibles. Con una mayor compatibilidad con la simultaneidad, Vert.x es adecuado no solo para IO, sino también para procesos con gran cantidad de CPU que requieren computación en paralelo.

Los bucles de eventos, sin embargo, son la mitad de la historia. La otra mitad tiene poco que ver con Vert.x.

Para conectarse a una base de datos, un cliente requiere un controlador de conector. En el ámbito de Java, el controlador más común para Sql es JDBC. El problema es que este controlador está bloqueando. Y está bloqueando a nivel de socket. Un hilo siempre se atascará allí hasta que regrese con una respuesta.

No hace falta decir que el conductor ha sido un cuello de botella al realizar una aplicación totalmente sin bloqueo. Afortunadamente, ha habido avances (aunque no oficiales) en un controlador asíncrono con varias bifurcaciones activas, entre ellas:

  • //github.com/jasync-sql/jasync-sql (para Postgres y MySql)
  • //github.com/reactiverse/reactive-pg-client (Postgres)

La regla de oro

Es bastante sencillo trabajar con Vert.x y se puede abrir un servidor http con unas pocas líneas de código.

El método requestHandler es donde el bucle de eventos entrega el evento de solicitud. Como Vert.x no tiene opiniones, manejarlo es estilo libre. Pero tenga en cuenta la única regla importante del hilo sin bloqueo: no lo bloquee.

Cuando trabajamos con concurrencia, podemos aprovechar tantas opciones disponibles en la actualidad, como Promise, Future, Rx, así como la forma idiomática de Vert.x. Pero a medida que crece la complejidad de una aplicación, tener una funcionalidad asíncrona por sí sola no es suficiente. También necesitamos la facilidad de coordinar y encadenar llamadas mientras evitamos el infierno de las devoluciones de llamada, además de pasar cualquier error con elegancia.

Scala Future satisface todas las condiciones anteriores con la ventaja adicional de estar basado en principios de programación funcional. Aunque este artículo no explora Scala Future en profundidad, podemos probarlo con una aplicación simple. Digamos que la aplicación es un servicio de API para encontrar un usuario dado su ID:

Hay tres operaciones involucradas: verificar el parámetro de solicitud, verificar si la identificación es válida y obtener los datos. Envolveremos cada una de estas operaciones en un Futuro y coordinaremos la ejecución en una estructura de "comprensión".

  • El primer paso es hacer coincidir la solicitud con un servicio. Scala tiene una potente función de coincidencia de patrones que podemos utilizar para este propósito. Aquí interceptamos cualquier mención de "/ usuario" y la pasamos a nuestro servicio.
  • El siguiente es el núcleo de este servicio donde nuestros futuros se organizan en una secuencia para la comprensión. La primera verificación futura de parámetros de envolturas f1 . Específicamente queremos recuperar la identificación de la solicitud de obtención y convertirla en int. (Scala no requiere un retorno explícito si el valor de retorno es la última línea del método). Como puede ver, esta operación podría generar una excepción ya que id podría no ser un int o ni siquiera estar disponible, pero eso está bien por ahora .
  • El segundo futuro f2 comprueba la validez de id. Bloqueamos cualquier ID inferior a 100 llamando explícitamente a Future.failed con nuestra propia CustomException. De lo contrario, pasamos un Future vacío en forma de Future.unit como validación exitosa.
  • El último futuro f3 recupera al usuario con la identificación proporcionada por f1. Como esto es solo una muestra, realmente no nos conectamos a una base de datos. Solo devolvemos un hilo simulado.
  • map ejecuta la disposición que produce los datos del usuario de f3 y luego los imprime en la respuesta.
  • Ahora bien, si en cualquier parte de la secuencia ocurre un error, se pasa un Throwable para recuperar . Aquí podemos hacer coincidir su tipo con una estrategia de recuperación adecuada. Mirando hacia atrás en nuestro código, hemos anticipado varias fallas potenciales, como una identificación faltante o una identificación que no era int o no válida, lo que arrojaría excepciones específicas. Estamos manejando cada uno de ellos en handleException pasando un mensaje de error al cliente.

Esta disposición proporciona no solo un flujo asincrónico desde el principio hasta el final, sino también un enfoque limpio para manejar errores. Y como se simplifica en todos los controladores, podemos centrarnos en cosas que importan, como la consulta de la base de datos.

Verticles, Event Bus y otras trampas

Vert.x también ofrece un modelo de concurrencia llamado verticle que se asemeja al sistema Actor. (Si desea obtener más información, consulte mi guía de Akka Actor). Verticle aísla su estado y comportamiento para proporcionar un entorno seguro para subprocesos. La única forma de comunicarse con él es a través de un bus de eventos.

Sin embargo, el bus de eventos Vert.x requiere que sus mensajes sean String o JSON. Esto dificulta el paso de objetos arbitrarios que no sean POJO. Y en un sistema de alto rendimiento, lidiar con la conversión JSON no es deseable ya que impone algunos costos de computación. Si está desarrollando aplicaciones de E / S, es mejor que no utilice vertical ni bus de eventos, ya que estas aplicaciones tienen poca necesidad de estado local.

Trabajar con algunos componentes de Vert.x también puede resultar bastante complicado. Puede encontrar falta de documentación, comportamiento inesperado e incluso fallas en el funcionamiento. Vert.x podría estar sufriendo por su propia ambición, ya que el desarrollo de nuevos componentes requeriría la migración a varios idiomas. Ésta es una empresa difícil. Por esa razón, lo mejor sería ceñirse al núcleo.

Si está desarrollando una API pública, vertx-core debería ser suficiente. Si es una aplicación web, puede agregar vertx-web que proporciona manejo de parámetros http y autenticación JWT / Session. Estos dos son los que dominaron los puntos de referencia de todos modos. Hay una disminución en el rendimiento en algunas pruebas para el uso de vertx-web, pero como parece que se deriva de la optimización, podría solucionarse en las versiones posteriores.