Compila una aplicación Spring de Kotlin con Google Cloud Platform

1. Introducción

Spring Framework 5.0 agregó compatibilidad exclusiva con Kotlin para que los desarrolladores de Kotlin puedan usar Spring con facilidad. En consecuencia, estos cambios significaron que las integraciones de Google Cloud proporcionadas por Spring Cloud GCP también funcionaron a la perfección en Kotlin. En este codelab, descubrirás lo fácil que es comenzar a usar los servicios de Google Cloud en tus aplicaciones de Kotlin.

En este codelab, se explica cómo configurar una aplicación de registro simple en Kotlin que demuestra el uso de los servicios de GCP, incluidos Cloud Pub/Sub y Cloud SQL.

Qué compilarás

En este codelab, configurarás una aplicación de Kotlin Spring Boot que acepte la información del registrante, la publique en un tema de Cloud Pub/Sub y la conserve en una base de datos de Cloud MySQL.

Qué aprenderás

Cómo realizar la integración con los servicios de Google Cloud en tu aplicación de Kotlin Spring

Requisitos

  • Un proyecto de Google Cloud Platform
  • Un navegador, como Chrome o Firefox

¿Cómo usarás este instructivo?

Ler Leer y completar los ejercicios

¿Cómo calificarías tu experiencia con la creación de aplicaciones web HTML/CSS?

Principiante Intermedio Avanzado

¿Cómo calificarías tu experiencia en el uso de los servicios de Google Cloud Platform?

Principiante Intermedio Avanzado .
.

2. Configuración y requisitos

Configuración del entorno de autoaprendizaje

  1. Accede a la consola de Cloud y crea un proyecto nuevo o reutiliza uno existente. (Si todavía no tienes una cuenta de Gmail o de G Suite, debes crear una).

dMbN6g9RawQj_VXCSYpdYncY-DbaRzr2GbnwoV7jFf1u3avxJtmGPmKpMYgiaMH-qu80a_NJ9p2IIXFppYk8x3wyymZXavjglNLJJhuXieCem56H30hwXtd8PvXGpXJO9gEUDu3cZw

ci9Oe6PgnbNuSYlMyvbXF1JdQyiHoEgnhl4PlV_MFagm2ppzhueRkqX4eLjJllZco_2zCp0V0bpTupUSKji9KkQyWqj11pqit1K1faS1V6aFxLGQdkuzGp4rsQTan7F01iePL5DtqQ

8-tA_Lheyo8SscAVKrGii2coplQp2_D1Iosb2ViABY0UUO1A8cimXUu6Wf1R9zJIRExL5OB2j946aIiFtyKTzxDcNnuznmR45vZ2HMoK3o67jxuoUJCAnqvEX6NgPGFjCVNgASc-lg

Recuerde el ID de proyecto, un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se mencionará más adelante en este codelab como PROJECT_ID.

  1. A continuación, deberás habilitar la facturación en la consola de Cloud para usar los recursos de Google Cloud recursos.

Ejecutar este codelab no debería costar mucho, tal vez nada. Asegúrate de seguir las instrucciones de la sección “Realiza una limpieza” en la que se aconseja cómo cerrar recursos para no incurrir en facturación más allá de este instructivo. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de $300.

Google Cloud Shell

Si bien Google Cloud se puede operar de manera remota desde tu laptop, en este codelab usaremos Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

Activar Cloud Shell

  1. En la consola de Cloud, haz clic en Activar Cloud ShellH7JlbhKGHITmsxhQIcLwoe5HXZMhDlYue4K-SPszMxUxDjIeWfOHBfxDHYpmLQTzUmQ7Xx8o6OJUlANnQF0iBuUyfp1RzVad_4nCa0Zz5LtwBlUZFXFCWFrmrWZLqg1MkZz2LdgUDQ.

zlNW0HehB_AFW1qZ4AyebSQUdWm95n7TbnOr7UVm3j9dFcg6oWApJRlC0jnU1Mvb-IQp-trP1Px8xKNwt6o3pP6fyih947sEhOFI4IRF0W7WZk6hFqZDUGXQQXrw21GuMm2ecHrbzQ

Si nunca ha iniciado Cloud Shell, aparecerá una pantalla intermedia (debajo de la mitad inferior de la página) que describe qué es. Si ese es el caso, haz clic en Continuar (y no volverás a verlo). Así es como se ve la pantalla única:

kEPbNAo_w5C_pi9QvhFwWwky1cX8hr_xEMGWySNIoMCdi-Djx9AQRqWn-__DmEpC7vKgUtl-feTcv-wBxJ8NwzzAp7mY65-fi2LJo4twUoewT1SUjd6Y3h81RG3rKIkqhoVlFR-G7w

El aprovisionamiento y la conexión a Cloud Shell solo tomará unos minutos.

pTv5mEKzWMWp5VBrg2eGcuRPv9dLInPToS-mohlrqDASyYGWnZ_SwE-MzOWHe76ZdCSmw0kgWogSJv27lrQE8pvA5OD6P1I47nz8vrAdK7yR1NseZKJvcxAZrPb8wRxoqyTpD-gbhA

Esta máquina virtual está cargada con todas las herramientas de desarrollo que necesitarás. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Gran parte de tu trabajo en este codelab, si no todo, se puede hacer simplemente con un navegador o tu Chromebook.

Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu ID del proyecto.

  1. En Cloud Shell, ejecuta el siguiente comando para confirmar que está autenticado:
gcloud auth list

Resultado del comando

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
gcloud config list project

Resultado del comando

[core]
project = <PROJECT_ID>

De lo contrario, puedes configurarlo con el siguiente comando:

gcloud config set project <PROJECT_ID>

Resultado del comando

Updated property [core/project].

3. Aprovisiona recursos de Pub/Sub

Primero, necesitaremos configurar un tema y una suscripción de Cloud Pub/Sub. En esta aplicación, publicaremos información de registro en un tema de Pub/Sub. la información se lee de este tema y se conserva en una base de datos.

En este instructivo, nos basaremos en Cloud Shell para aprovisionar nuestros recursos. Ten en cuenta que también se pueden configurar los recursos de Pub/Sub a través de la sección de Cloud Pub/Sub en la consola de Google Cloud.

En tu terminal de Cloud Shell, primero habilita la API de Pub/Sub.

$ gcloud services enable pubsub.googleapis.com

A continuación, crearemos un tema de Pub/Sub llamado registrations para esta aplicación. La información de registro que se envíe a través de la solicitud se publicará en este tema.

$ gcloud pubsub topics create registrations

Por último, crea una suscripción para el tema. Una suscripción a Pub/Sub te permite recibir mensajes de un tema.

$ gcloud pubsub subscriptions create registrations-sub --topic=registrations

Ya completaste la creación de un tema y una suscripción de Cloud Pub/Sub para tu aplicación.

4. Crea una instancia y una base de datos de Cloud SQL (MySQL)

Para nuestra aplicación de muestra, también necesitamos configurar una instancia de base de datos que contenga la información del registrante. Este paso también dependerá de la terminal de Cloud Shell para aprovisionar los recursos de Cloud SQL. Ten en cuenta que también puedes ver y configurar tus instancias de Cloud SQL a través de la consola de Google Cloud.

Primero, habilita la API de Cloud SQL Admin.

$ gcloud services enable sqladmin.googleapis.com

A continuación, aprovisionaremos una instancia de Cloud SQL (MySQL). Este comando puede tardar un poco.

$ gcloud sql instances create codelab-instance --region=us-east1

Después de crear correctamente tu instancia de Cloud SQL, crea una nueva base de datos en tu instancia llamada registrants.

$ gcloud sql databases create registrants --instance codelab-instance

Ya completaste la configuración de la instancia y la base de datos de Cloud SQL para tu aplicación.

5. Inicializa una aplicación de Spring Boot

Ahora estamos listos para comenzar a escribir la aplicación. En los próximos pasos, se seguirá usando Cloud Shell descrito en los pasos de configuración.

Primero, usaremos Initializr para generar el código de andamiaje del proyecto. En la ventana de Cloud Shell, ejecuta lo siguiente:

$ cd ~
$ curl https://start.spring.io/starter.tgz \
  -d language=kotlin \
  -d bootVersion=2.4.0 \
  -d dependencies=web,data-jpa,integration,cloud-gcp-pubsub,thymeleaf \
  -d baseDir=registrations-codelab | tar -xzvf -
$ cd registrations-codelab

Con este comando, se genera una configuración inicial del proyecto de Maven, así como un código de andamiaje para tu aplicación en el directorio registrations-codelab/. En las siguientes secciones, se describen las ediciones de código necesarias para producir una aplicación que funcione.

Editor de código de Cloud Shell

La forma más sencilla de comenzar a modificar y ver el código en el entorno de Cloud Shell es usar el editor de código de Cloud Shell integrado.

Una vez que hayas abierto una instancia de Cloud Shell, haz clic en el ícono de lápiz para abrir el editor de código. El editor debería permitirte modificar directamente los archivos del proyecto producidos por Initialzr.

cce293b40119c37b.png

6. Configuración de la base de datos

Primero, configura tu aplicación para que pueda conectarse a la base de datos de Cloud MySQL que configuraste. Las bibliotecas de Spring Cloud GCP ofrecen un activador de Cloud MySQL que proporciona las dependencias necesarias para conectarse a una instancia de Cloud MySQL.

Agrega la dependencia spring-cloud-gcp-starter-sql-mysql al proyecto pom.xml:

registrations-codelab/pom.xml

...
<dependencies>

  ... Other dependencies above ...

  <!-- Add the MySQL starter to the list of dependencies -->
  <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId>
  </dependency>
</dependencies>

Además, debes modificar el archivo de configuración application.properties para describir la configuración de tu base de datos. Copia las siguientes propiedades en tu archivo application.properties.

Busca el nombre de conexión de la instancia con tu base de datos:

$ gcloud sql instances describe codelab-instance \
  --format 'value(connectionName)'

El resultado se usará en el archivo application.properties para configurar la información de conexión.

src/main/resources/application.properties

# Modify this property using the output from the previous command line.
spring.cloud.gcp.sql.instance-connection-name=INSTANCE_CONNECTION_NAME

# Your database name
spring.cloud.gcp.sql.database-name=registrants

# So app starts despite "table already exists" errors.
spring.datasource.continue-on-error=true

# Enforces database initialization
spring.datasource.initialization-mode=always

# Cloud SQL (MySQL) only supports InnoDB, not MyISAM
spring.jpa.database-platform=org.hibernate.dialect.MySQL55Dialect
spring.jpa.hibernate.ddl-auto=create-drop

# This is used if you want to connect to a different database instance
# user other than root; not used in codelab.
# spring.datasource.username=root

# This is used to specify the password of the database user;
# not used in codelab.
# spring.datasource.password=password

La única propiedad que debes modificar es el nombre de la conexión con la instancia. Este valor debe expresarse como un valor separado por dos puntos con el siguiente formato: YOUR_GCP_PROJECT_ID:REGION:DATABASE_INSTANCE_NAME.

7. Creación del contenido estático

Primero, crearemos el frontend para nuestra aplicación. La solicitud debe tener un formulario que permita a alguien registrar personas físicas y, además, una vista que muestre todas las personas registradas correctamente.

Para la página principal, crea una index.html que contenga el formulario de registro.

src/main/resources/static/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Registration Sample Application</title>
</head>
<body>

<h1>Registration</h1>

<div>
  <nav>
    <a href="/">Home</a><br>
    <a href="/registrants">Registered People</a><br>
  </nav>

  <p>
    This is a demo registration application which sends user information to a Pub/Sub topic and
    persists it into a MySQL database.
  </p>

  <h2>Register Person</h2>
  <div>
    <form action="/registerPerson" method="post">
      First Name: <input type="text" name="firstName" />
      Last Name: <input type="text" name="lastName" />
      Email: <input type="text" name="email" />
      <input type="submit" value="Submit"/>
    </form>
  </div>
</div>

</body>
</html>

A continuación, crearemos una plantilla Thymeleaf llamada registrants.html para mostrar los usuarios registrados. Thymeleaf es un framework de plantillas que usamos para construir y publicar código HTML creado de forma dinámica. Verás que la plantilla se ve como HTML, excepto que tiene algunos elementos de Markdown adicionales para administrar contenido dinámico. Esta plantilla acepta un solo parámetro llamado personsList, que contiene todos los registrantes que se registraron a través de la aplicación.

src/main/resources/templates/registrants.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Registrants List</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<h1>Registrants List</h1>
<p>
  This page displays all the people who were registered through the Pub/Sub topic.
  All results are retrieved from the MySQL database.
</p>
<table border="1">
  <tr>
    <th>First Name</th>
    <th>Last Name</th>
    <th>Email</th>
  </tr>
  <tr th:each="person : ${personsList}">
    <td>[[${person.firstName}]]</td>
    <td>[[${person.lastName}]]</td>
    <td>[[${person.email}]]</td>
  </tr>
</table>

</body>
</html>

En este punto, puedes verificar que se esté entregando el contenido estático.

Compila y ejecuta la app con Maven:

$ ./mvnw spring-boot:run

Haz clic en el botón Vista previa de la ventana de Cloud Shell y verifica que veas la página principal renderizada. Sin embargo, ninguna de las funciones de la IU funcionará porque nos falta un controlador web. Se agregará en el siguiente paso.

5e38bb0d0e93002e.png

Después de obtener una vista previa de la aplicación, presiona CTRL+C para cerrarla.

8. Cómo enviar a los registrantes a un tema de Pub/Sub

En este paso, implementaremos la función cuando los registrantes enviados a través del formulario web se publicarán en un tema de Cloud Pub/Sub.

Agrega las clases de datos

Primero, crearemos algunas clases de datos de Kotlin: Estas serán nuestras entidades JPA y también actuarán como nuestra representación intermedia de las personas registradas enviadas a través del formulario.

En el paquete de demostración, agrega dos archivos nuevos: una clase Person y un PersonRepository de Spring Data. Estas dos clases nos permitirán almacenar y recuperar con facilidad entradas de registro de la base de datos de MySQL con Spring Data JPA.

src/main/kotlin/com/example/demo/Person.kt

package com.example.demo

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id

@Entity
data class Person(
    val firstName: String,
    val lastName: String,
    val email: String,
    @Id @GeneratedValue
    var id: Long? = 0)

src/main/kotlin/com/example/demo/PersonRepository.kt

package com.example.demo

import org.springframework.data.repository.CrudRepository

interface PersonRepository : CrudRepository<Person, Long>

Agrega el controlador web

A continuación, crearemos una clase de controlador que procesa a los registrantes del formulario y envía la información al tema de Cloud Pub/Sub que creó anteriormente. Este controlador crea dos extremos:

  • /registerPerson: Es el extremo POST en el que se envía la información del registrante y, luego, se envía al tema de Pub/Sub. En la función registerPerson(..), la información del registrante se envía al tema de Pub/Sub a través de PubSubTemplate, una clase de conveniencia de las integraciones de Pub/Sub de Spring Cloud GCP que minimiza el código estándar necesario para comenzar a interactuar con Cloud Pub/Sub.
  • /registrants: Muestra todos los registrantes que se registraron correctamente en la base de datos. Esta información se recupera de la instancia de MySQL con el repositorio de Spring Data que creamos en el paso anterior.

Crea la siguiente clase de controlador en el paquete de demostración:

src/main/kotlin/com/example/demo/Controller.kt

package com.example.demo

import com.google.cloud.spring.pubsub.core.PubSubTemplate
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.ModelAndView
import org.springframework.web.servlet.view.RedirectView

@RestController
class Controller(val pubSubTemplate: PubSubTemplate, val personRepository: PersonRepository) {
  
  // The Pub/Sub topic name created earlier.
  val REGISTRATION_TOPIC = "registrations"

  @PostMapping("/registerPerson")
  fun registerPerson(
    @RequestParam("firstName") firstName: String,
    @RequestParam("lastName") lastName: String,
    @RequestParam("email") email: String): RedirectView {

    pubSubTemplate.publish(
        REGISTRATION_TOPIC,
        Person(firstName, lastName, email))
    return RedirectView("/")
  }

  @GetMapping("/registrants")
  fun getRegistrants(): ModelAndView {
    val personsList = personRepository.findAll().toList()
    return ModelAndView("registrants", mapOf("personsList" to personsList))
  }
}

El controlador lee la información del registrante enviada a través del formulario web y, luego, la publica en el tema de Pub/Sub.

Agrega el Bean del asignador de objetos JSON

Es posible que hayas notado en el controlador que publicamos un objeto Person en el tema de Pub/Sub y no en una cadena. Esto es posible porque aprovechamos la compatibilidad de Spring Cloud GCP para que las cargas útiles personalizadas de JSON se envíen a los temas. Las bibliotecas te permiten serializar objetos a JSON, enviar cargas útiles de JSON a un tema y deserializar la carga útil cuando se recibe.

Para aprovechar esta función, debemos agregar un bean ObjectMapper al contexto de tu aplicación. Este bean ObjectMapper se usará para serializar objetos hacia JSON y desde este cuando tu aplicación envía y recibe mensajes. En la clase DemoApplication.kt, agrega Spring bean JacksonPubSubMessageConverter:

src/main/kotlin/com/example/demo/DemoApplication.kt

package com.example.demo

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

// new imports to add
import org.springframework.context.annotation.Bean
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.cloud.spring.pubsub.support.converter.JacksonPubSubMessageConverter

@SpringBootApplication
class DemoApplication {
  // This bean enables serialization/deserialization of
  // Java objects to JSON for Pub/Sub payloads
  @Bean
  fun jacksonPubSubMessageConverter(objectMapper: ObjectMapper) = 
      JacksonPubSubMessageConverter(objectMapper)
}

fun main(args: Array<String>) {
        runApplication<DemoApplication>(*args)
}

En este punto, puedes intentar ejecutar la aplicación nuevamente con el siguiente comando:

$ ./mvnw spring-boot:run

Desde el formulario web de la página principal, la aplicación enviará la información al tema de Pub/Sub que creaste. Sin embargo, aún no hace nada útil porque todavía necesitamos leer desde ese tema de Pub/Sub. Esto se logra en el siguiente paso.

9. Lee los registrantes del tema de Pub/Sub

En el último paso, procesaremos la información del registrante del tema de Pub/Sub y conservaremos la información en la base de datos de Cloud MySQL. Esta acción completará la solicitud, lo que te permitirá enviar nuevas personas registradas a través del formulario y ver a todos los usuarios registrados a través del extremo /registrants.

Esta aplicación aprovechará Spring Integration, que ofrece muchas abstracciones convenientes para administrar la mensajería. Agregaremos una PubSubInboundChannelAdapter para permitirnos leer mensajes del tema de Pub/Sub y colocarlos en pubsubInputChannel para continuar con el procesamiento. Luego, configuraremos la función messageReceiver con @ServiceActivator para que se invoque con los mensajes que lleguen a pubsubInputChannel.

src/main/kotlin/com/example/demo/DemoApplication.kt

package com.example.demo

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

import org.springframework.context.annotation.Bean
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.cloud.gcp.pubsub.support.converter.JacksonPubSubMessageConverter

// new imports to add
import com.google.cloud.spring.pubsub.core.PubSubTemplate
import com.google.cloud.spring.pubsub.integration.AckMode
import com.google.cloud.spring.pubsub.integration.inbound.PubSubInboundChannelAdapter
import com.google.cloud.spring.pubsub.support.BasicAcknowledgeablePubsubMessage
import com.google.cloud.spring.pubsub.support.GcpPubSubHeaders
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.integration.annotation.ServiceActivator
import org.springframework.integration.channel.DirectChannel
import org.springframework.messaging.MessageChannel
import org.springframework.messaging.handler.annotation.Header

@SpringBootApplication
class DemoApplication {

  private val REGISTRANT_SUBSCRIPTION = "registrations-sub"

  @Autowired
  private lateinit var personRepository: PersonRepository

  // New Spring Beans to add
  @Bean
  fun pubsubInputChannel() = DirectChannel()

  @Bean
  fun messageChannelAdapter(
      @Qualifier("pubsubInputChannel") inputChannel: MessageChannel,
      pubSubTemplate: PubSubTemplate): PubSubInboundChannelAdapter {

    val adapter = PubSubInboundChannelAdapter(
        pubSubTemplate, REGISTRANT_SUBSCRIPTION)
    adapter.outputChannel = inputChannel
    adapter.ackMode = AckMode.MANUAL
    adapter.payloadType = Person::class.java
    return adapter
  }

  @ServiceActivator(inputChannel = "pubsubInputChannel")
  fun messageReceiver(
      payload: Person,
      @Header(GcpPubSubHeaders.ORIGINAL_MESSAGE) message: BasicAcknowledgeablePubsubMessage) {
    personRepository.save(payload)
    print("Message arrived! Payload: $payload")
    message.ack()
  }

  // ObjectMapper bean from previous step
  @Bean
  fun jacksonPubSubMessageConverter(objectMapper: ObjectMapper) = JacksonPubSubMessageConverter(objectMapper)
}

fun main(args: Array<String>) {
        runApplication<DemoApplication>(*args)
}

En este punto, ya completaste la configuración de la aplicación. Para verificar que la app funcione correctamente, ejecuta lo siguiente:

$ ./mvnw spring-boot:run

Vuelve a hacer clic en el botón Vista previa y completa el formulario y envíalo para registrar un usuario.

e0d0b0f0c94120c2.png

Haga clic en el vínculo Personas registradas para verificar que el nuevo registrante aparezca en la tabla.

ab3b980423d0c51.png

¡Felicitaciones! Eso es todo. Para finalizar la aplicación, presiona CTRL+C en la ventana de terminal.

10. Limpieza

Para limpiar tu entorno, debes borrar el tema de Pub/Sub y la instancia de Cloud MySQL que creaste.

Borra la instancia de Cloud MySQL

$ gcloud sql instances delete codelab-instance

Borra los recursos de Pub/Sub

$ gcloud pubsub subscriptions delete registrations-sub
$ gcloud pubsub topics delete registrations

11. ¡Felicitaciones!

Ya terminaste de escribir una aplicación de Spring Kotlin que se integra con Cloud Pub/Sub y Cloud SQL (MySQL).

Más información

Licencia

Este trabajo cuenta con una licencia Atribución 2.0 Genérica de Creative Commons.