Kotlin, Camel y CXF

Mock del primer servicio

Una vez que tenemos la base del proyecto, podemos agregar nuestro primer servicio REST usaremos la referencia JAX-RS 2 con la implementacion Apache CXF.

Nuestro primer servicio sera un endpoint de preguntas, con dos operaciones la primera consultar una lista de preguntas, de momento escritas en el mismo codigo, y la segunda de esa lista de preguntas obtener una en particular por su codigo.

Prerequisitos

De momento el unico requerimiento previo es tener nuestro proyecto como lo dejamos en el primer post.

Mensajes de Camel

Apache camel al igual que EJB estan basados en los principios de lo que ahora conocemos como microservicios antes de que incluso se llamaran asi, pequenias aplicaciones que al sumarse todas dan como resultado un proceso complejo, cada microservicio se comunica con otros para resolver la tarea en especifica que tiene asignada.

Pues bien en una ruta interactuan diversos procesos, dependiendo del contenido de los datos se toman decisiones de flujo y se consume o envia informacion a diferentes endpoints. La forma en que Camel comparte informacion entre los diferentes endpoints, protocolos y procesos, es mediante el uso de Mensajes, que son muy parecidos a los mensajes HTTP tienen un header y un body, el body es el contenido mismo del mensaje, mientras que el header es meta-data que sirve para manipular o modificar de algun modo el body.

En todo punto de decision de camel existe un mensaje de entrada, que es el que se recibe y un mensaje de salida que es el que se envia al siguiente punto del proceso, si estamos en un endpoint de entrada el mensaje de entrada se genera con base en el protocolo definido (HTTP por el momento) y si es un endpoint de salida el mensaje se transfiere con el protocolo adecuado.

Desarrollo

Configurando dependencias

El primer paso es actualizar nuestras dependencias en el archivo build.gradle:

  • compile "org.apache.camel:camel-cxf:$camel_version"
  • compile group: 'org.apache.cxf', name: 'cxf-rt-transports-http-jetty', version: '3.2.1'
  • compile group: 'com.google.code.gson', name: 'gson', version: '2.8.2'

Definiendo el API del servicio

A mi en lo personal me gusta mas trabajar con paquetes por modulo en lugar de paquetes por tipo de componente, sobre todo en mantenimiento, por lo que vamos a crear el paquete com.betotto.question para ello hay que dar clic secundario en el folder src/main/Java -> new Package.

Este paquete es ahora el modulo question, en este modulo vamos a crear un nuevo Kotlin File/Class, dando clic secundario en el paquete que acabamos de crear, le ponemos por nombre QuestionApi con el siguiente contenido:

 

Estamos creando una interfaz que define un Resource en el estilo de JAX-RS, basicamente tenemos dos operaciones GET una en el path "/" y otra en el path "/idQuestion" idQuestion es el id de la pregunta que vamos a consultar, ambas producen JSON como respuesta, en Kotlin los metodos se llaman funciones, por lo que ambas funciones regresan un tipo Response, podriamos dejar los objetos como respuesta en lugar de Response y dejar que Gson los convierta automaticamente a JSON, pero a mi me gusta usar Response, mas adelante les explico porque.

Definiendo la clase de datos

Lo que sigue es definir una clase que contenga los campos que describen una Question (voy a spanglear un poco con los nombres de clases y variables), con sus correspondientes getters y setters, con un constructor para que solamente podamos crear Questions con Id, con el texto de la pregunta inicializado en cadena vacia y que el Id no se pueda modificar una vez creado el objeto, para ello creamos un nuevo Kotlin File/Class en el modulo question (paquete com.betotto.question) de nombre Question con el contenido:

 

Si, a mi tambien me gusto mucho esto. los getters y setter son por defecto en Kotlin val no se puede modificar (es immutable) y el constructor es la misma declaracion de la clase, por cierto con question de tipo String inicializada en "".

Enum para operaciones

Yo esperaba que camel definiera una ruta por cada operacion que realiza el endpoint, pero para eso necesitas usar el Rest DSL y aqui no lo estamos usando, en java DSL hace una sola ruta para todas la operaciones, por lo que crear una clase que contenga el nombre de las operaciones sera util para evitar errores de tipado y simplificar un poco las cosas, para ello en el paquete question, creamos un nuevo Kotlin File/Class de nombre QuestionOperations:

 

Procesando la entrada

Vamos a definir que hacemos con los mensajes que recibamos por el Api definida, para eso usamos Processors y vamos a definir el processor de entrada, que nos permitira preparar los mensajes para ser manipulados como necesitemos, creamos un nuevo Kotlin File/Class QuestionIn en el modulo question:

 

Esta clase implementa la interfaz Processor, el cual sobreescribe la funcion process y recibimos el Exchange, en este objeto estan el mensaje de entrada y el mensaje de salida, tomamos la operacion actual del header del mensaje de entrada (funcion del Api correspondiente al tipo de operacion HTTP) y con ella decidimos que hacer dependiendo la operacion, para este proceso en especifico nota que solo estamos creando un objeto Question para la operacion getQuestionById y lo estamos poniendo como body de la salida, tambien copiamos los header de la entrada en los header de la salida.

Asi como se puede ver solo nos enfocamos a la logica en especifico de este proceso, que basicamente es validar los tipos de entrada, dejando las respuestas y decisiones para otras partes del proceso. A mi parecer esto es genial :).

Procesando la salida

Ahora vamos a definir como entregamos los mensajes, para ello creamos otro Processor que construira las respuestas del servicio. Creamos un nuevo Kotlin File/Class QuestionOut:

 

Aqui es donde se puede ver porque me gusta usar Response, soy de la vieja escuela y me gusta manipular manualmente los status de REST, en este processor creamos la lista de Questions, y dependiendo de la operacion getAllQuestions o getQuestionById, regresamos la lista de questions en formato JSON o filtramos la Question que queremos basados en el Id y regresamos esa question, o un 404 si no existe dicha Question.

Finalmente las ultimas dos lineas crean la respuesta y lo colocan en el mensaje de salida. Me gusto mucho como el filtro de Arrays en Kotlin es muy parecido al de javascript Array.filter(element => isFilter(element)) :P .

Creando la ruta

Ya tenemos todos los elementos necesarios para armar nuestro servicio, simplemente falta crear la ruta, para ello necesitamos un nuevo Kotlin File/Class QuestionRoute

 

Decimos que el endpoint es cxfrs sobre http (con jetty como base, Camel sabe solito no necesitamos decirle), usando el host y el puerto del archivo camel.properties, el path es wizard/question y en ese path va a montar nuestro Recurso el cual es com.betotto.question.QuestionApi, le damos un Id a la ruta y una descripcion, finalmente le decimos que el primer proceso es QuestionIn y despues QuestionOut.

De nuestro anterior post sabemos que un HTTP endpoint regresa el ultimo mensaje de la ruta que en nuestro caso es el Response que QuestionOut coloca en el body de salida del exchange. :D

Instalando la ruta

Lo ultimo que hay que hacer es agregar la ruta al contexto de Camel en el archivo Main.kt

 

Yo borre la ruta anterior porque solo era de ejemplo. Checa que no tengas problemas de puertos porque creo que no pueden convivir.

Y de a deveras funciona ?

Damos click secundario en el archivo src/main/java/Main.kt -> Run Main.kt la consola debera decir algo asi:

 

Abrimos nuestro navegador favorito

Get All

Get ById

Not found

Este ultimo checa en la consola de desarrollo el status http

Notas finales

Checa que a veces se hace uso de `in` eso pasa porque in es reservado en Kotlin pero Camel necesita usar eso, asi que Kotlin hace uso de eso para notar la diferencia entre algo que no es Kotlin necesariamente (o sea que es java).

Subi el proyecto a github, este es el commit

Notaras que QuestionIn no es necesario para este post, pero el porque esta ahi sera descrito en el siguiente post.