miércoles, 21 de junio de 2017

Tutorial: servicio REST con Kotlin y Spring Boot para "dummies"

Hoy haremos un pequeño servicio REST "Greeter" muy simple, así que al final de este tutorial cada uno de vosotros puede saltar de alegría "yo se hacer servicios REST!".
El codigo esta en el GitHub:
https://github.com/cyberglad/kotlindemo

Este servicio simplemente nos va decir "Hola, <tu nombre>". Al inicio no necesitamos mas. Pero solo al inicio. No vas a poner en tu currículum un servicio "Hola mundo!", entonces enseguida lo vamos a hacer mas inteligente y redirigir las respuestas según que parametro le pasas.

Kotlin - un lenguaje de la familla Java, pero mas "pragmático", breve y conciso y se parece un poco a Groovy. Que por supuesto también utiliza la JVM.
IntelliJ IDEA - hace poco que descubierto esta IDE y estoy encantado, especialmente me encanta la integration con Kotlin (normal, visto que las 2 cosas pertenecen a la misma sociedad - JetBrains). Os recomiendo mucho: tiene muy buen "texto predictivo" (por fin he encontrado uno que tiene sentido) casi como MS Visual Studio y mucho mejor que Eclipse. También el entorno es muy claro, los "pom.xml" de Maven por fin se ven bien estructurados.
Spring Boot - pa alegrarnos la vida y no enrollarnos con el Tomcat.
Spring Initializer (https://start.spring.io): para tener lista la estructura del proyecto Maven con el pom.xml

Kotlin + IntelliJ + Spring Boot = crea un entorno rápido y eficaz sin perder el tiempo en chorradas tipo "como crear mi proyecto Maven y importar todas las bibliotecas necesarias".


Creación del Proyecto

1) Utilizamos Spring Initializer que nos genera el proyecto Maven listo con su estructura (vamos a https://start.spring.io/)



1) Seleccionamos "Maven" como proyecto, "Kotlin" como lenguaje y la versión Spring Boot 1.5.4 (tb he probado otras - funciona igual).
2) Como "dependencies" necesitaremos solo el Jersey JAX-RS
3) Ya esta! Venga, a descargar el proyecto! Lo guardamos en el folder, donde la IDEA guarda todos los proyectos - C:\Users\<tu nombre>\IdeaProjects, mira la captura e pantalla abajo..

Importación en IntelliJ IDEA

1) Instalamos la IDEA, la abrimos y vamos a "File->Open":
2) Simplemente seleccionamos la carpeta de nuestro proyecto y damos a "Ok".

3) Esperamos un poco para que la IDEA pueda importar todo y nos creara las carpetas "src" con "main" y "test"con la clase principal y donde vamos a meter nuestro código. Nota que la carpeta "main" contiene la carpeta "kotlin" en vez de "java".
4) En la carpeta "kotlin" encontramos nuestro paquete principal com.projects.kotlin.kotlindemo con la clase principal KotlindemoApplication que desde el inicio tiene buena pinta:

package com.projects.kotlin.kotlindemo

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
@SpringBootApplicationclass KotlindemoApplication

fun main(args: Array<String>) {
    SpringApplication.run(KotlindemoApplication::class.java, *args)
}

Que chulo este syntax, no? La manera de declarar el método con la palabra "fun" o variables con doble-punto! No hay ni "public", ni "static", ni punto y coma al final.


Creación del controlador

1) Lanzamos esta clase tal cual y con mucha alegría descubrimos que todo va bien, nuestro proyecto se compila sin errores y el Tomcat escucha a la puerta 8080 (miraremos en el log de Spring Boot):
Tomcat started on port(s): 8080 (http)
2) Pero vamos a crear algo útil: creamos un paquete "restful" y el controlador Spring - una clase Kotlin GreetController(botón derecho del ratón->New->New Kotlin Class):



La clase la declaramos "open" y le ponemos 2 anotaciones: "@Component" (ALT+Enter para importar la biblioteca necesaria) para declarar un servicio y @Path("greet/{name}"  (ALT+Enter para importar la biblioteca necesaria) para especificar el camino de acceso.
3) Luego creamos el método "greet" (con anotaciones correctas - @GET y @Produces para declarar que volveremos algo mas que una simple String):

@Component
@Path("greet/{name}")
open class GreetController{

    @GET    
    @Produces(MediaType.APPLICATION_JSON)
    fun greet(@PathParam("name") name:String): Response {
        return Response.ok(name).build()
    }
}

4) Ahora creamos una clase de la configuración Jersey, sino Jersey no se sabe configurar correctamente. Para eso heredemos la clase JerseyConfig de la clase  ResourceConfig:

import org.glassfish.jersey.server.ResourceConfig
import org.springframework.stereotype.Component

@Component
class JerseyConfig: ResourceConfig
{
    constructor() {
        packages(JerseyConfig:: class.java.`package`.name)
    }
}

La declaramos "open", pq normalmente Kotlin hace las clases "cerradas" para el acceso externo.
Me hace gracia, como se crea el constructor (con la palabra "constructor", sino como?)!

JerseyConfig:: class.java.`package`.name

Lo que en Java se hubiera hecho con JerseyConfig.class, aqui se hace con JerseyConfig:: class.java
Luego sacamos el "package" con la palabra "package", y, como en Kotlin esta "reservada", le ponemos las comillas. Al final ponemos el nombre.del package (en Java eso se hacía con .getPackage()).

Lanzamiento

1) Voila! lanzaremos este servicio para ver nuestro nombre. El lanzamiento se hace en Intellij IDEA en 2 maneras:

2) Si no hay nada rojo en el log de la aplicación, la ultima frase nos dice que la aplicación esta lanzada:
Started KotlindemoApplicationKt in 5.216 seconds (JVM running for 6.136)
Iremos en nuestro browser a:
http://localhost:8080/greet/<escribe tu nombre>
Y aprovecha a ver una pagina blanca con tu nombre escrito. En mi caso es "Yuri":


Extras

Ahora vamos a hacer algo mas complicado:

- evitamos que un nombre especifico ("R21", podía ser el nombre de un robot malo que quiere hackear nuestro servicio) vea lo que vuelve nuestro servicio.  Cuando R21 intenta a acceder al servicio - tiene que ver un error, los demás - no.
- añadimos la fecha y hora al saludo

Mi servicio tiene que distinguir el usuario R21 de los demás.  Como se realiza eso? Normalmente se crea una excepción, donde metemos la lista de los usuarios "malos". Nuestro servicio mira el parámetro del nombre y si el nombre corresponde al "nombre malo", le asigna esta excepción y le envia un mensaje de error.
Yo no quiero utilizar las excepciones y Kotlin me va ayudar en eso.

1) Creamos un nuevo paquete "service" donde metemos nuestro servicio (una nueva clase GreetService).



2) En esta clase creamos una interface Resp para la respuesta (la creamos directamente dentro la clase).
Dentro esta interface creamos 2 clases para 2 tipos de respuesta (Success y Error) que implementan la interface (mira que facil):

import org.springframework.stereotype.Component

@Component
class GreetService{

    interface Resp {
        class Success(val msg: String): Resp
        class Error(): Resp
    }
    fun greet(name: String): Resp {
        return if (name.equals("R21"))
            Resp.Error()
        else            
            Resp.Success("Hello, ${name}")
    }
}

También necesitaremos el método "greet" que acepta nuestro nombre y que va devolver la respuesta del tipo "Success" o del tipo "Error" (si el nombre es "R21").
3) Toca modificar también nuestro controlador:

package com.projects.kotlin.kotlindemo.restful
import com.projects.kotlin.kotlindemo.service.GreetService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Componentimport java.util.*
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.PathParamimport javax.ws.rs.Producesimport javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response

@Component
@Path("greet/{name}")
open class GreetResource @Autowired constructor(val service: GreetService){

    data class Greet(val message: String, val time:String)


    @GET    @Produces(MediaType.APPLICATION_JSON)
    fun greet(@PathParam("name") name:String): Response {
        val resp = service.greet(name)
        return when(resp){
            is GreetService.Resp.Success -> Response.ok(resp.msg, Date().toString()).build()
            is GreetService.Resp.Error -> Response.status(Response.Status.BAD_REQUEST).build()
            else->Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()
        }
        return Response.ok(name).build()
    }
}

Explicación:
La primera linea es típica para Kotlin: la declaración de la case + inyección del servicio @Autowired +el constructor - todo en la misma linea a la vez!

Creamos nuestra respuesta:

val resp = service.greet(name)

y declaramos, si el usuario es "R21" - devolvemos el error, si es otro - un saludo + la fecha y la hora(y si otra cosa - Internal Server Error):

     return when(resp){
            is GreetService.Resp.Success -> Response.ok(resp.msg).build()
            is GreetService.Resp.Error -> Response.status(Response.Status.BAD_REQUEST).build()
            else->Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()
      }

 
4) Lanzaremos nuestro servicio y veremos la pagina con el saludo para un usuario "bueno":


Metemos "R21" como nombre y veremos la pagina para el usuario "malo":


Conclusión:

Hoy hemos conseguido no solo hacer un estúpido servicio REST, pero también hacerlo distinguir los usuarios a la manera inteligente.









No hay comentarios:

Publicar un comentario