Header Ads Widget

Ticker

6/recent/ticker-posts

Creación de API en la JVM con Kotlin y Spark - Parte 2

 

Creación-de-API-JVM-kotlin-spark-java-nordic-apis-pt-2

Si está creando API o microservicios en la máquina virtual Java (JVM), debe comprobar el micro-framework Spark . Este pequeño conjunto de herramientas está diseñado a partir de un marco Ruby similar llamado Sinatra, que proporciona una biblioteca que facilita la creación de API y sitios web. Gran parte de esta simplicidad proviene del uso de nuevas características de lenguaje introducidas en Java 8, como lambdas, que brindan a los programadores una forma elegante de definir sus API.

En esta segunda publicación de nuestra serie sobre Kotlin , un nuevo lenguaje de programación de Jetbrains, le mostraremos cómo puede hacer que el modelo de programación sea aún más dulce utilizando este nuevo lenguaje JVM. Lo usaremos para construir algunos componentes adicionales que necesitará para crear API realmente geniales usando Spark . Partiendo de la introducción de Spark anterior que publicamos , los nuevos componentes que crearemos en esta parte de nuestra serie le brindarán un punto de partida útil para aprovechar Spark en sus API, al tiempo que demuestra el potencial y el poder de Kotlin.

Vea a Travis Spencer presentando este tema en el encuentro de Java de Estocolmo

Introducción resumida a Spark

En nuestra introducción a Spark , explicamos que Spark es un conjunto de herramientas que se vincula a su API para definir y enviar rutas a funciones que manejan las solicitudes realizadas a los puntos finales de su API (es decir, un multiplexor o enrutador). Está diseñado para que la definición de estas rutas sea rápida y sencilla. Debido a que Spark está escrito en Java y ese es su idioma de destino, proporciona un medio simple para hacerlo utilizando las lambdas de Java 8. Tampoco se basa en anotaciones o archivos de configuración XML como lo hacen algunos marcos comparables, lo que facilita la puesta en marcha.

Un ejemplo típico de Hello World (que también incluimos en nuestra introducción de Spark ) es este:

import static spark.Spark.*;

public class HelloWorld {
    public static void main(String[] args) {
            get("/hello", (request, response) -> "Hello World");
    }
}

Tenga en cuenta que este fragmento es Java y no Kotlin; llegaremos a nuestra versión de Kotlin en un momento.

Cuando se ejecuta, Spark iniciará un servidor web que servirá a nuestra API. Compile, ejecute esto y navegue hasta Cuando se golpea, Spark llamará a la lambda asignada con el método. Esto resultará en un tono de marcación.http://localhost:4567/hellospark.Spark.get

Consulte nuestra introducción de Spark para obtener más información sobre el historial de Spark, las capacidades de enrutamiento, incluidos los comodines en las rutas, el procesamiento de solicitudes / respuestas y la salida de plantillas.

Construyendo una API robusta con Spark

En nuestra introducción (que incluye varios enlaces a documentos y muestras adicionales ), puede ver que Spark es un marco útil por sí solo. Diseñado específicamente para realizar enrutamiento, puede iniciar un servidor web y puede conectarse con ciertos motores de plantillas. Sin embargo, estas son las únicas campanas y silbidos que obtendrá con él. Cuando comience a usarlo para crear una API de calibre de producción, necesitará más.

Antes de poner en marcha su API, probablemente escribirá mucho código. Para que esto sea evolutivo, comprobable y compatible, también necesitará:

  • Controladores : Spark le brinda una forma de modelar sus datos y vistas para presentarlos, pero no existe el concepto de controladores. Los necesitará si va a seguir el patrón Model View Controller (MVC) (que debería).
  • Inyección de dependencias (DI) : para que su API sea más modular y robusta, necesitará una forma de invertir la resolución de las dependencias , lo que permitirá intercambiarlas fácilmente en las pruebas. Spark no proporciona una integración lista para usar con ningún marco de inyección de dependencia (DI) en particular.
  • Localización : Spark facilita la definición de vistas utilizando cualquier número de motores de plantillas, pero la resolución de ID de mensajes va más allá de lo que proporciona el marco. Lo necesitará si se dirige a un mercado global con su API.
  • Independencia del servidor : Spark inicia Jetty de forma predeterminada. Si desea utilizar otro servidor o una versión diferente de Jetty, tendrá que hacer un trabajo adicional. Esto es trivial si está distribuyendo su aplicación como WAR , pero tendrá que escribir algún código si este no es el caso.

Si toma Spark, su soporte para varios lenguajes de plantilla, y agrega estas cosas, tiene un conjunto de herramientas muy completo para crear API. Si envuelve todo esto en una API fluida usando Kotlin y utiliza este lenguaje para construir sus controladores, podrá desarrollar rápidamente su servicio.

No le mostraremos cómo extender Spark con todo lo anterior, pero profundizaremos en los dos primeros. Envíe una solicitud de extracción con sus ideas sobre cómo admitir otras funciones.

Cabe señalar que existen otros marcos para crear API que ya los incluyen (por ejemplo, Spring Boot ). Sin embargo, con estos, las bibliotecas de terceros utilizadas (si las hay) y cómo se proporcionan estas funciones, ya están configuradas. Si desea utilizar un marco DI diferente, por ejemplo, puede que no sea posible. Si es así, puede incurrir en hinchazón ya que el marco de DI proporcionado no es necesario en su caso. Con Spark, estas decisiones son suyas (para bien o para mal).

Plantilla en Kotlin

Explicamos el soporte de plantillas de Spark en nuestra introducción. Hablamos sobre cómo el kit de herramientas viene con soporte para media docena de motores de plantilla que puede usar para generar respuestas. En esta publicación, queremos mostrarle cómo se puede hacer esto con la muestra basada en Kotlin que hemos estado construyendo. Usamos el mismo modelo de objetos de Spark, pero incluimos nuestra propia API fluida que hace que la sintaxis sea mucho más limpia. Con esta versión azucarada, generar una respuesta con una plantilla es casi lo mismo que sin ella. Puedes ver esto en el punto de entrada del servicio donde definimos con fluidez todas las rutas:

route(
    path("/login", to = LoginController::class, renderWith = "login.vm"),
    path("/authorize", to = AuthorizeController::class, renderWith = "authorize.vm"),
    path("/token", to = TokenController::class))

Aquí estamos pasando el nombre de la plantilla en el renderWith argumento nombrado . Puede leer más sobre la pathfunción en la primera parte de esta serie , pero la parte importante a tener en cuenta aquí es que, a diferencia de nuestro ejemplo de plantilla simple basado en Java , el modelo de datos no se confunde en la definición de las rutas: que se deja a los controladores. En este punto, solo estamos definiendo qué plantilla se debe usar con qué ruta.

También puede definir este tipo de azúcar sintáctico en Java. La muestra de Kotlin se escribió originalmente en ese idioma y se convirtió utilizando las herramientas proporcionadas por Jetbrains. Antes de ser Kotlinizada, la API de Spark estaba envuelta en una API más conveniente que se ajustaba mejor a nuestro modelo de uso. Puede ver la versión antigua de Java en el historial de GitHub , pero basta con decir que la versión de Kotlin es mucho más limpia.

Agregar capacidades de DI a una API basada en Spark

Para implementar DI en su API, puede usar varios marcos, incluidos Guice , Pico y el nativo de Kotlin, Injekt . Cualquiera que sea su decisión, deberá integrarlos con Spark. En esta subsección, lo guiaremos a través de esto usando Pico .

Está más allá del alcance de este artículo presentar DI. Si no sabe cómo funciona este patrón, consulte el artículo introductorio de Jacob Jenkov sobre DI .

La integración de Pico se maneja en nuestra Applicationclase que hereda de la SparkApplicationclase. Utiliza otra de nuestras clases llamada Routerlos dos son lo que une a Spark y Pico. Las partes importantes de la Applicationclase se muestran en la siguiente lista:

public class Application(
var composer: Composable = Noncomposer(), var appContainer: MutablePicoContainer = DefaultPicoContainer(), var routes: () -> List) : SparkApplication { private var router = Router() init { composer.composeApplication(appContainer) } fun host() { // Explained below... } // ...
}

El constructor de laApplication clase toma tres argumentos:

  1. Una instancia de tipo Composableque por defecto es un objeto que no realiza ninguna composición de dependencias (p. Ej., En casos simples donde la API no usa DI)
  2. MutuablePicoContainerque albergará las dependencias de la API
  3. La función lambda que producirá las rutas (como se describe en la parte 1 de esta serie ).

Para ver más claramente cómo esta clase conecta Pico y Spark, debemos observar cómo componimos las dependencias. Luego hablaremos sobre el Routeren detalle.

Composición de dependencias

Con DI, un objeto no crea una instancia de sus dependencias directamente. En cambio, la creación de objetos se invierte y los objetos dependientes se proporcionan , no se crean. Para hacer esto, un contenedor DI debe llenarse con objetos y resolutores de objetos. En nuestro modelo de API de muestra , esto se hace a través de subtipos de la Composableinterfaz. Un compositor registra las dependencias de la API, relacionando una interfaz con una implementación concreta que deberían utilizar todos los objetos que dependen de esa interfaz. Los objetos se resuelven en varios niveles o ámbitos, lo que da como resultado una jerarquía de resolución de objetos . También podemos crear fábricas y agréguelos a los contenedores; estos producirán objetos de los que otros dependen, lo que nos permitirá realizar una creación de objetos complicada fuera de los compositores.

A medida que el Applicationobjeto cobra vida y initse llama al método, lo primero que hará es componer las dependencias de la aplicación. Hace esto usando el dado ComposableEsta interfaz tiene este aspecto:

interface Composable {

    fun composeApplication(appContainer: MutablePicoContainer) { }

    fun composeRequest(container: MutablePicoContainer) { }
}

Como puede ver, los compositores hacen dos cosas:

  1. Componga las dependencias de toda la aplicación de la API
  2. Redactar dependencias que deberían tener un alcance a nivel de solicitud (es decir, objetos que solo existen durante la vida útil de una solicitud / respuesta HTTP)

El primer método, composeApplication es lo que llama el método de la Applicationclase initEste método se llama una vez, cuando se inicia el servidor API. El último método,, composeRequestes llamado por solicitud de la Routerclase (descrito a continuación).

En el compositor, puede registrar dependencias de cualquier forma que Pico admita. Ofrece una serie de mecanismos muy útiles que la convierten en una buena herramienta para considerar su uso en la implementación de su API. Si bien no profundizaremos en Pico en esta publicación, le mostraremos una implementación simple de unComposable subclase que se incluye en el proyecto de muestra :

class ContainerComposer : Composable
{
    public override fun composeApplication(appContainer: MutablePicoContainer)
    {
        appContainer.addComponent(javaClass())
        appContainer.addComponent(javaClass())
        appContainer.addComponent(javaClass())
    }

    public override fun composeRequest(container: MutablePicoContainer) { }
}

Este compositor en particular es bastante tonto, el tuyo probablemente será mucho más complicado. Las cosas importantes aquí son que:

  • Los controladores están en el contenedor de la aplicación.
  • Todas sus dependencias se resolverán desde la aplicación o el contenedor de solicitudes cuando se obtengan instancias de ellas en el Router .

Esto facilitará la creación de controladores porque sus dependencias se les darán cuando cobren vida. (Esto se facilitará aún más si se utiliza una reflexión que configura automáticamente las rutas, que explicaremos a continuación).

Resolución de dependencias a medida que se enrutan las solicitudes

La Routerclase trabaja con la Applicationclase para unir todos estos marcos. A medida que agregue soporte para la localización y cosas más avanzadas que no se tratan en esta publicación, encontrará que su versión se vuelve bastante compleja. Por el bien de esta publicación, mantendremos nuestra muestra relativamente simple.

Routerhereda SparkBase, para que pueda acceder a su addRoutemétodo protegido Esta API chispa de bajo nivel es lo que se llama por los métodos de nivel superior estáticas, getpost, etc., los cuales fueron discutidos en nuestra introducción de chispa y se muestra en el Mundo Hola listado superior. No los usamos, sino que usamos nuestra propia API fluida que termina invocando esta interfaz Spark de nivel inferior. Nuestro Routerexpone un método público, routeToque puede ver aquí:

class Router constructor() : SparkBase() 
{
    public fun  routeTo(
        path: String, container: PicoContainer, controllerClass: Class,
        composer: Composable, template: String? = null) 
    {
        // ...
    }
}

El routeTométodo se llama en la Applicationclase para cada ruta que se configura con nuestro DSL. Puede ver esto en el hostmétodo de Application(que se eliminó de la lista anterior de esa clase):

fun host()
{
    var routes = routes.invoke() // Invoke the lambda that produces all the routes

    for (routeData in routes)
    {
        val (path, controllerClass, template) = routeData

        router.routeTo(path, appContainer, controllerClass, composer, template)
    }
}

Consulte la entrega anterior de esta serie para obtener información sobre la routeData clase de datos y cómo se utiliza para asignar varios valores simultáneamente .

Cuando routeTo se llama así, hace dos cosas importantes:

  1. Busca reflexivamente los métodos definidos en el controlador dado para determinar qué métodos HTTP deben enrutarse; y
  2. Prueba para ver si se ha asignado una plantilla a la ruta y, de ser así, llama a diferentes sobrecargas de la ruta. SparkBase clase para registrar la ruta de manera adecuada.

Puede ver esto en el código fuente con más claridad, pero la parte importante se muestra a continuación. Observe cómo se llaman dos métodos privados, addRouteaddTemplatizedRoute, para cada método en el controlador que se encuentran mediante la reflexión .

if (template == null || template.isBlank()) {
    addRoute(methodName, path, container, controllerClass, composer)
}
else {
    addTemplatizedRoute(methodName, template, path, container, controllerClass, composer)
}

Tenga en cuenta aquí que usamos un smartcast para convertir la templatevariable anulable en una cadena después de verificar primero si es nula. Esta es una de las características más interesantes de Kotlin.

Independientemente de si se debe usar o no una plantilla, ambos métodos privados crean una RouteImplinstancia de Spark Para hacer esto, se debe crear una instancia de un cierre y pasarlo al addRoutemétodo de Spark En el caso de una ruta con plantilla, el cierre y RouteImplse crean así:

val r = fun (request: Request, response: Response): ModelAndView
{
    var model = router(request, response, container, controllerClass, composer)

    return ModelAndView(model, template)
}

SparkBase.addRoute(httpMethod, TemplateViewRouteImpl.create(path, r, VelocityTemplateEngine()))

Hacemos esto de manera similar en el método que maneja el caso sin plantilla (que se puede ver en el repositorio de origen).

La parte de la nota no es que el Routerprivado 's routemétodo es llamado en el cierre, rEste puntero de función, y por lo tanto route, se llama con cada solicitud. Así es como podemos integrar DI a nivel de solicitud.

El routemétodo comienza creando un nuevo contenedor de nivel de solicitud que tiene el contenedor de toda la aplicación como principal. Esto hará que las dependencias se resuelvan en primer lugar desde el contenedor secundario a nivel de solicitud. Solo si no se encuentran allí, se investigará al padre. (Esta es la resolución de dependencia jerárquica a la que aludimos anteriormente). Luego, llamamos al composeRequestmétodo del compositor , pasando este nuevo contenedor. Una vez que se realiza la composición, buscamos el controlador del contenedor y lo invocamos.

Puede ver esto en el siguiente fragmento:

private fun  router(request: Request, response: Response, appContainer: PicoContainer,
                                        controllerClass: Class, composer: Composable) : Map<String, Any>
{
   val requestContainer = DefaultPicoContainer(appContainer)
   var model : Map<String, Any> = emptyMap()

   composer.composeRequest(requestContainer)

   try
   {
       val controller = requestContainer.getComponent(controllerClass)

       // ...
   }
   catch (e: Exception)
   {
       halt(500, "Server Error")
   }

   return model
}

Volveremos a este método un poco más adelante cuando analicemos los controladores, pero esto le brinda una buena descripción general de cómo integrar un marco DI como Pico con Spark. Para obtener más detalles, revise la fuente o deje un comentario a continuación.

Java Meetup Medium CTA-01

Implementación de lógica API en controladores

A medida que construye su API, es muy probable que tenga docenas, cientos o incluso miles de puntos finales . Cada uno de estos tendrá una lógica diferente : validar entradas, llamar a servicios de back-end, buscar información en un almacén de datos, y la lista continúa. Este procesamiento debe realizarse de manera ordenada o, de lo contrario, su base de código no se podrá mantener . Para evitar esto, la lógica de su API debe estar encapsulada en un controlador por punto final.

Con Spark, obtienes un sistema de enrutamiento que se envía a las funciones. Puede usar lambdas como en las muestras, pero esto se vuelve insostenible a medida que aumenta el tamaño de su API. Una vez que haya pasado la fase de prototipo , se dará cuenta de que esto no es suficiente. Hay muchas formas de agregar esta capacidad a Spark, y esto es lo que lo convierte en un excelente marco. Al igual que con DI, usted es libre de elegir la forma que mejor se adapte a sus necesidades (para bien o para mal). En esta publicación, le ofreceremos una sugerencia que satisfará estos objetivos:

  • Debería ser rápido y fácil crear controladores.
  • Los programadores no deberían tener que centrarse en cómo se realiza el enrutamiento mientras construyen controladores.
  • Todas las dependencias de un controlador deben inyectarse mediante inyección de constructor.
  • Un controlador no debe estar abarrotado de un montón de anotaciones ruidosas.

Con estos objetivos en mente, comenzamos con el Controllabletipo (que mencionamos en el último post ). Cada controlador dentro de nuestra API heredará de esta clase.

abstract class Controllable 
{
    public open fun before(request: Request, response: Response): Boolean = true
    public open fun get(request: Request, response: Response): ControllerResult = ControllerResult()
    public open fun post(request: Request, response: Response): ControllerResult = ControllerResult()
    public open fun put(request: Request, response: Response): ControllerResult = ControllerResult()
    public open fun delete(request: Request, response: Response): ControllerResult = ControllerResult()
    public open fun patch(request: Request, response: Response): ControllerResult = ControllerResult()
    public open fun head(request: Request, response: Response): ControllerResult = ControllerResult()
    public open fun trace(request: Request, response: Response): ControllerResult = ControllerResult()
    public open fun connect(request: Request, response: Response): ControllerResult = ControllerResult()
    public open fun options(request: Request): ControllerResult = ControllerResult()
    public open fun after(request: Request, response: Response) { }
}

La extensión de este tipo no requiere que la subclase anule ningún método. En la práctica, esto no sucedería, ya que eso significaría que no se establecerían rutas y el servidor no respondería a las solicitudes. Sin embargo, el punto es que los controladores solo necesitan implementar las acciones que requieren, no otras, lo que hace que implementar una sea simple y rápido.

En el código de muestra , hemos definido tres controladores que simulan la lógica del flujo de código OAuth :

  1. AuthorizeController
  2. LoginController
  3. TokenController

Si no está familiarizado con el funcionamiento de este intercambio de mensajes, le explicaremos brevemente :

En primer lugar, un usuario accede al punto final autorizado realizando un HTTP GET a /authorizeSi el usuario no está autenticado, se le redirige a /loginAllí se les presenta una vista que les permite identificarse. Si tienen éxito, se les redirige de nuevo a /authorizeAhora, al encontrar una sesión autenticada, el punto final autorizado muestra una pantalla de consentimiento (otra vista). Si el usuario permite que el cliente actúe en su nombre, se le da un código de uso único que envía al punto final del token que emite un token de acceso al final. Hay mucho más, así que profundice en OAuth si esta es la lógica que debe implementar su API.

Para lograr nuestro objetivo de simplificar la creación de estos controladores y no sobrecargar al programador con rutas y anotaciones, usamos la reflexión para descubrir cuáles de los Controllablemétodos de la clase se han anulado. Esto se hace Routerjusto antes de llamar al addRoutemétodo de Spark (descrito anteriormente):

public fun  routeTo(path: String, container: PicoContainer, controllerClass: Class,
                                          composer: Composable, template: String? = null)
{
    for (classMethod in controllerClass.getDeclaredMethods())
    {
        val methodName = classMethod.getName()

        for (interfaceMethod in javaClass().getMethods())
        {
            if (methodName == interfaceMethod.getName() && // method names match?
                    classMethod.getReturnType() == interfaceMethod.getReturnType() && // method return the same type?
                    Arrays.deepEquals(classMethod.getParameterTypes(), interfaceMethod.getParameterTypes())) // Params match?
            {
                // Call templatized or non-templatized version of Spark's addRoute method (shown above) to 
                // get route wired up

                break
            }
        }
    }
}

Esto es tomar la clase de controlador que pasamos al pathmétodo (en el punto de entrada de la API) y verificar cada uno de sus métodos para ver si el nombre, el tipo de retorno y los tipos de parámetros coinciden con alguno de los definidos en elControllable clase base. Si es así, se configura una ruta, lo que hace que se llame a ese método cuando se solicita la ruta con la acción correcta.

Para usar la reflexión de Java de Kotlin de esta manera, debe asegurarse de tener kotlin-reflect.jaren su classpath (además de kotlin-runtime.jar). Si está utilizando Maven , agregue esta dependencia a su POM de esta manera:



org.jetbrains.kotlin
kotlin-reflect
${kotlin.version}

Para hacer esto más concreto, veamos AuthorizeControllercuál es el primero que se llama en nuestro flujo OAuth simplificado:

public class AuthorizeController : Controllable()
{
    public override fun before(request: Request, response: Response): Boolean
    {
        if (request.session(false) == null)
        {
            // No session exists. Redirect to login
            response.redirect("/login")

            // Return false to abort any further processing
            return false
        }

        return true
    }

    // ...
}

La parte importante aquí es el beforemétodo, que no se enruta. Spark tiene este tipo de filtros de procesamiento previo y posterior , pero no los usamos porque queremos abortar la llamada al método enrutado si beforedevuelve falso. Entonces, tenemos nuestros propios filtros antes / después que la Routerclase usa para implementar este algoritmo en el routermétodo. Esto se hace justo después de crear y componer el contenedor de solicitudes (descrito anteriormente):

if (controller.before(request, response))
{
   // Fire the controller's method depending on the HTTP method of the request
   val httpMethod = request.requestMethod().toLowerCase()
   val method = controllerClass.getMethod(httpMethod, javaClass(), javaClass())
   val result = method.invoke(controller, request, response)

   if (result is ControllerResult && result.continueProcessing)
   {
       controller.after(request, response)

       model = result.model
   }
}

Esta condición if será falsa para AuthorizationControllercuando el usuario no haya iniciado sesión. Por lo tanto, el GET realizado por el cliente nunca se enviará al getmétodo del controlador En cambio, la redirección en elbefore filtro hará que el usuario sea enviado al punto final de inicio de sesión.

El LoginControllermaneja el GET realizado por el cliente que sigue al redireccionamiento. Se les presenta la vista que se asoció con ese punto final. Esto permite al usuario ingresar sus credenciales y enviarlas al mismo controlador. Para procesar esto, LoginControllertambién anula Controllableel postmétodo de esta manera:

public override fun post(request: Request, response: Response): ControllerResult
{
    var session = request.session() // Create session

    // Save the username in the session, so that it can be used in the authorize endpoint (e.g., for consent)
    session.attribute("username", request.queryParams("username"))

    // Redirect back to the authorize endpoint now that "login" has been performed
    response.redirect("/authorize")

    return ControllerResult(continueProcessing = false)
}

Aquí, creamos una sesión para el usuario usando la Sessionclase de Spark (que es una envoltura delgada javax.servlet.http.HttpSession), guardando el nombre de usuario para su procesamiento posterior. Luego, redirigimos al usuario al archivo AuthorizeController(También abortamos el procesamiento posterior en este método, lo que provoca afterque no se llame al método del controlador). Cuando el usuario sigue este redireccionamiento, el beforefiltro de AuthorizeControllerdevolverá verdadero, lo que permitirá que el token de acceso se emita esta vez asegurándose de que el anuladoget Se llama al método .

Admitimos que este ejemplo de OAuth es artificial, pero muestra cómo puede agregar controladores a Spark y cómo estos pueden inyectar sus dependencias usando DI. Con las sutilezas de la sintaxis de Kotlin, incluso podemos facilitar la conexión de todos estos componentes. Bifurque la muestra y vea si puede crear un prototipo de la lógica de su API. Si no funciona, por favor háganoslo saber en un comentario!

Conclusión y próximos pasos

Esta serie ha sido un par de publicaciones largas con la introducción de Spark intercalada en el medio. Sin embargo, si ha leído hasta aquí, ahora tiene tres herramientas poderosas en su cinturón de herramientas API:

  • La máquina virtual de Java : una plataforma abierta que prioriza la compatibilidad con versiones anteriores, lo que garantiza la longevidad de su código.
  • Una flora de armazones : Spark, Pico y los otros demostrados en esta serie son solo la punta del iceberg de lo que está disponible en el ecosistema de Java.
  • Kotlin : un lenguaje de código abierto desarrollado por Jetbrains, un precursor de la comunidad Java.

Esta tríada lo hará más productivo y lo ayudará a ofrecer API de mayor calidad en menos tiempo. Usando el texto estándar desarrollado durante esta serie , ahora tiene un punto de partida para comenzar aún más rápido. Twobo Technologies planea adoptar Kotlin ahora para código de no envío e incluirlo en el código de producto tan pronto como se publique la versión 1.0 del lenguaje. Le recomendamos que utilice la muestra y sus nuevos conocimientos para formular una hoja de ruta similar.

Otro lugar para aplicar este nuevo conocimiento es en los hackatones. Al participar en tales eventos, es importante elegir sus herramientas con anticipación y programar rápidamente. Usar Spark y Kotlin te ayudará a hacer esto. Uno de esos hackathon que se llevará a cabo próximamente en Estocolmo es el próximo BattleHack de PayPal, que tendrá lugar el 14 y 15 de noviembre. Los boletos para eso saldrán a la venta esta semana, ¡así que consígalos ahora y use su experiencia Kotlin / Spark para crear una API que podría ganar un trofeo de hacha y $ 100,000!

reunirse

Además, si se encuentra en Estocolmo, asista a la próxima reunión del grupo de usuarios de Java que tendrá lugar el 25 de agosto en SUP46. En este evento, hablaremos sobre la integración de Spark y Kotlin en particular. Además, también profundizaremos en Clojure y Groovy. Si no puedes ir a Estocolmo, grabaremos la charla de Kotlin, así que suscríbete a nuestro canal de YouTube. hoy y captelo tan pronto como salga.

Realmente esperamos que haya disfrutado de esta serie y agradecemos sus comentarios y correcciones a continuación, en Twitter o Facebook. .

[Divulgación: PayPal y JetBrains son patrocinadores de la reunión de Java Estocolmo producida por las API nórdicas]

Publicar un comentario

0 Comentarios