Criar um aplicativo Kotlin Spring com o Google Cloud Platform

1. Introdução

O Spring Framework 5.0 adicionou suporte dedicado ao Kotlin, facilitando o uso do Spring para desenvolvedores Kotlin. Consequentemente, essas mudanças fizeram com que as integrações do Google Cloud fornecidas pelo Spring Cloud GCP (link em inglês) também funcionassem perfeitamente no Kotlin. Neste codelab, você verá como é fácil começar a usar os serviços do Google Cloud nos seus aplicativos Kotlin.

Este codelab mostra como configurar um aplicativo de registro simples em Kotlin que demonstra o uso de serviços do GCP, como o Cloud Pub/Sub e o Cloud SQL.

O que você vai criar

Neste codelab, você vai configurar um aplicativo Kotlin Spring Boot que aceita informações do responsável pelo registro, publica essas informações em um tópico do Cloud Pub/Sub e as mantém em um banco de dados do Cloud MySQL.

O que você vai aprender

Como fazer a integração com os serviços do Google Cloud no seu aplicativo Kotlin Spring.

O que é necessário

  • Um projeto do Google Cloud Platform
  • Um navegador, como o Chrome ou o Firefox

Como você vai usar este tutorial?

Apenas leitura Leitura e exercícios

Como você classificaria sua experiência com a criação de apps da Web HTML/CSS?

Iniciante Intermediário Proficiente

Como você classificaria sua experiência com o uso dos serviços do Google Cloud Platform?

Iniciante Intermediário Proficiente

2. Configuração e requisitos

Configuração de ambiente autoguiada

  1. Faça login no Console do Cloud e crie um novo projeto ou reutilize um existente. Crie uma se você ainda não tiver uma conta do Gmail ou do G Suite.

dMbN6g9RawQj_VXCSYpdYncY-DbaRzr2GbnwoV7jFf1u3avxJtmGPmKpMYgiaMH-qu80a_NJ9p2IIXFppYk8x3wyymZXavjglNLJJhuXieCem56H30hwXtd8PvXGpXJO9gEUDu3cZw

ci9Oe6PgnbNuSYlMyvbXF1JdQyiHoEgnhl4PlV_MFagm2ppzhueRkqX4eLjJllZco_2zCp0V0bpTupUSKji9KkQyWqj11pqit1K1faS1V6aFxLGQdkuzGp4rsQTan7F01iePL5DtqQ

8-tA_Lheyo8SscAVKrGii2coplQp2_D1Iosb2ViABY0UUO1A8cimXUu6Wf1R9zJIRExL5OB2j946aIiFtyKTzxDcNnuznmR45vZ2HMoK3o67jxuoUJCAnqvEX6NgPGFjCVNgASc-lg

Lembre-se do código do projeto, um nome exclusivo em todos os projetos do Google Cloud. O nome acima já foi escolhido e não servirá para você. Faremos referência a ele mais adiante neste codelab como PROJECT_ID.

  1. Em seguida, será necessário ativar o faturamento no Console do Cloud para usar os recursos do Google Cloud.

A execução deste codelab não será muito cara, se for o caso. Siga todas as instruções na seção "Limpeza", que orienta você sobre como encerrar recursos para não incorrer em cobranças além deste tutorial. Novos usuários do Google Cloud estão qualificados para o programa de US$ 300 de avaliação sem custos.

Google Cloud Shell

Embora o Google Cloud possa ser operado remotamente do seu laptop, neste codelab vamos usar o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.

Ativar o Cloud Shell

  1. No Console do Cloud, clique em Ativar o Cloud ShellH7JlbhKGHITmsxhQIcLwoe5HXZMhDlYue4K-SPszMxUxDjIeWfOHBfxDHYpmLQTzUmQ7Xx8o6OJUlANnQF0iBuUyfp1RzVad_4nCa0Zz5LtwBlUZFXFCWFrmrWZLqg1MkZz2LdgUDQ.

zlNW0HehB_AFW1qZ4AyebSQUdWm95n7TbnOr7UVm3j9dFcg6oWApJRlC0jnU1Mvb-IQp-trP1Px8xKNwt6o3pP6fyih947sEhOFI4IRF0W7WZk6hFqZDUGXQQXrw21GuMm2ecHrbzQ

Se você nunca tiver iniciado o Cloud Shell, verá uma tela intermediária (abaixo da dobra) com a descrição do que ele é. Se esse for o caso, clique em Continuar e você não o verá novamente. Esta é uma tela única:

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

Leva apenas alguns instantes para provisionar e se conectar ao Cloud Shell.

pTv5mEKzWMWp5VBrg2eGcuRPv9dLInPToS-mohlrqDASyYGWnZ_SwE-MzOWHe76ZdCSmw0kgWogSJv27lrQE8pvA5OD6P1I47nz8vrAdK7yR1NseZKJvcxAZrPb8wRxoqyTpD-gbhA

Essa máquina virtual contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Praticamente todo o seu trabalho neste codelab pode ser feito em um navegador ou no seu Chromebook.

Depois de se conectar ao Cloud Shell, você já estará autenticado e o projeto já estará configurado com seu ID do projeto.

  1. Execute o seguinte comando no Cloud Shell para confirmar que você está autenticado:
gcloud auth list

Resposta ao 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

Resposta ao comando

[core]
project = <PROJECT_ID>

Se o projeto não estiver configurado, configure-o usando este comando:

gcloud config set project <PROJECT_ID>

Resposta ao comando

Updated property [core/project].

3. Provisionar recursos do Pub/Sub

Primeiro, é preciso configurar um tópico e uma assinatura do Cloud Pub/Sub. Neste aplicativo, publicaremos informações de registro em um tópico do Pub/Sub. Depois, as informações são lidas nesse tópico e mantidas em um banco de dados.

Neste tutorial, usaremos o Cloud Shell para provisionar nossos recursos. Também é possível configurar recursos do Pub/Sub na seção do Cloud Pub/Sub no console do Google Cloud.

No terminal do Cloud Shell, primeiro ative a API Pub/Sub.

$ gcloud services enable pubsub.googleapis.com

Em seguida, vamos criar um tópico do Pub/Sub chamado registrations para este aplicativo. As informações de registro enviadas por meio do aplicativo serão publicadas neste tópico.

$ gcloud pubsub topics create registrations

Por fim, crie uma assinatura para o tópico. Uma assinatura do Pub/Sub permite receber mensagens de um tópico.

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

Você acabou de criar um tópico e uma assinatura do Cloud Pub/Sub para seu aplicativo.

4. Criar uma instância e um banco de dados do Cloud SQL (MySQL)

Para nosso exemplo de aplicativo, também precisamos configurar uma instância de banco de dados para manter as informações do responsável pelo registro. Esta etapa também vai depender do terminal do Cloud Shell para provisionar os recursos do Cloud SQL. Também é possível visualizar e configurar suas instâncias do Cloud SQL pelo Console do Google Cloud.

Primeiro, ative a API Cloud SQL Admin.

$ gcloud services enable sqladmin.googleapis.com

Em seguida, provisionaremos uma instância do Cloud SQL (MySQL). Esse comando pode levar algum tempo.

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

Depois de criar a instância do Cloud SQL, crie um novo banco de dados chamado registrants na instância.

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

Você concluiu a configuração da instância e do banco de dados do Cloud SQL para seu aplicativo.

5. Inicializar um aplicativo Spring Boot

Agora estamos prontos para começar a escrever o aplicativo. As próximas etapas continuarão usando o Cloud Shell descrito nas etapas de configuração.

Primeiro, vamos usar Initializr para gerar o código de scaffolding para o projeto. Na janela do Cloud Shell, execute:

$ 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

Esse comando gera uma configuração inicial do projeto Maven e um código de scaffolding para seu aplicativo no diretório registrations-codelab/. As seções a seguir descrevem as edições de código necessárias para produzir um aplicativo funcional.

Editor de código do Cloud Shell

A maneira mais fácil de começar a modificar e visualizar código no ambiente do Cloud Shell é usar o editor de código do Cloud Shell integrado.

Depois de abrir uma instância do Cloud Shell, clique no ícone de lápis para abrir o editor de código. O editor permite que você modifique diretamente os arquivos de projeto produzidos pelo Initialzr.

cce293b40119c37b.png

6. Configuração do banco de dados

Primeiro, configure seu aplicativo para que ele possa se conectar ao banco de dados do Cloud MySQL que você definiu. As bibliotecas do Spring Cloud GCP oferecem uma inicialização do Cloud MySQL que fornece as dependências necessárias para se conectar a uma instância do Cloud MySQL.

Adicione a dependência spring-cloud-gcp-starter-sql-mysql ao pom.xml do projeto:

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>

Além disso, você precisa modificar o arquivo de configuração application.properties para descrever a configuração do seu banco de dados. Copie as seguintes propriedades no arquivo application.properties.

Encontre o nome da conexão da instância no seu banco de dados:

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

A saída será usada no arquivo application.properties para configurar as informações de conexão.

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

A única propriedade que você precisa modificar é o nome da conexão da instância. Esse valor precisa ser formatado como um valor separado por dois-pontos com o formato: YOUR_GCP_PROJECT_ID:REGION:DATABASE_INSTANCE_NAME.

7. Como criar o conteúdo estático

Primeiro, vamos criar o front-end do aplicativo. A inscrição deve ter um formulário que permita que alguém registre indivíduos e também uma visualização que mostre todos os inscritos bem-sucedidos.

Para a página inicial, crie um index.html contendo o formulário 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>

Em seguida, criaremos um modelo Thymeleaf chamado registrants.html para exibir os usuários registrados. Thymeleaf é uma estrutura de modelos que usamos para construir e disponibilizar HTML criado dinamicamente. Você verá que o modelo se parece com HTML, exceto por conter alguns elementos de marcação adicionais para manipular conteúdo dinâmico. Este modelo aceita um único parâmetro chamado personsList, que contém todos os responsáveis pelo registro que foram registrados pela inscrição.

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>

Neste ponto, é possível verificar se o conteúdo estático está sendo exibido.

Crie e execute o app usando o Maven:

$ ./mvnw spring-boot:run

Clique no botão "Visualizar" na janela do Cloud Shell e verifique se a página inicial está sendo renderizada. Nenhuma das funcionalidades na interface vai funcionar, porque um controlador da Web está ausente. Isso será adicionado na próxima etapa.

5e38bb0d0e93002e.png

Depois de visualizar o aplicativo, pressione CTRL+C para encerrá-lo.

8. Como enviar os registrados para um tópico do Pub/Sub

Nesta etapa, vamos implementar o recurso em que os inscritos enviados pelo formulário on-line serão publicados em um tópico do Cloud Pub/Sub.

Adicionar as classes de dados

Primeiro, vamos criar algumas classes de dados do Kotlin. essas entidades serão nossas entidades JPA e também atuarão como nossa representação intermediária dos responsáveis pelo registro enviados por meio do formulário.

No pacote de demonstração, adicione dois novos arquivos: uma classe Person e um PersonRepository do Spring Data. Essas duas classes nos permitirão armazenar e recuperar facilmente as entradas de registro do banco de dados MySQL usando o 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>

Adicionar o controlador da Web

Em seguida, vamos criar uma classe de controlador que processa os inscritos do formulário e envia as informações para o tópico do Cloud Pub/Sub criado anteriormente. Esse controlador cria dois endpoints:

  • /registerPerson: o endpoint POST em que as informações do responsável pelo registro são enviadas e depois enviadas para o tópico do Pub/Sub. Na função registerPerson(..), as informações do responsável pelo registro são enviadas para o tópico do Pub/Sub usando PubSubTemplate, uma classe de conveniência das integrações do Spring Cloud GCP Pub/Sub que minimiza o código boilerplate necessário para começar a interagir com o Cloud Pub/Sub.
  • /registrants: mostra todos os responsáveis pelo registro registrados no banco de dados. Essas informações são recuperadas da instância do MySQL usando o repositório Spring Data que criamos na etapa anterior.

Crie a seguinte classe Controller no pacote de demonstração:

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))
  }
}

O controlador lê as informações do responsável pelo registro enviadas pelo formulário da Web e publica as informações no tópico do Pub/Sub.

Como adicionar o JSON Object Mapper Bean

Talvez você tenha notado no Controlador que publicamos um objeto Person no tópico do Pub/Sub, e não uma string. Isso é possível porque aproveitamos o suporte do Spring Cloud GCP para o envio de payloads JSON personalizados para tópicos. As bibliotecas permitem que você serialize objetos para JSON, envie payloads JSON para um tópico e desserialize o payload quando ele for recebido.

Para aproveitar esse recurso, precisamos adicionar um bean ObjectMapper ao contexto do seu aplicativo. Esse bean ObjectMapper será usado para serializar objetos de e para o JSON quando seu aplicativo enviar e receber mensagens. Na classe DemoApplication.kt, adicione o bean Spring 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)
}

Neste ponto, tente executar o aplicativo novamente executando:

$ ./mvnw spring-boot:run

A partir do formulário da Web na página principal, o aplicativo enviará as informações para o tópico do Pub/Sub que você criou. No entanto, ele ainda não está fazendo nada útil porque ainda precisamos ler esse tópico do Pub/Sub. Isso será feito na próxima etapa.

9. como ler registrados como registrados no tópico do Pub/Sub

Na etapa final, vamos processar as informações do responsável pelo registro do tópico do Pub/Sub e manter as informações no banco de dados do Cloud MySQL. A inscrição vai ser concluída, e você poderá enviar novos inscritos pelo formulário e conferir todos os usuários registrados pelo endpoint /registrants.

Este aplicativo utilizará a Spring Integration, que oferece muitas abstrações convenientes para lidar com mensagens. Adicionaremos um PubSubInboundChannelAdapter para permitir a leitura das mensagens do tópico do Pub/Sub e colocá-las no pubsubInputChannel para processamento adicional. Em seguida, configuraremos a função messageReceiver usando @ServiceActivator para ser invocada com as mensagens que chegam ao 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)
}

Neste ponto, você concluiu a configuração do aplicativo. Para verificar se o app funciona corretamente, execute:

$ ./mvnw spring-boot:run

Clique no botão Visualizar novamente e tente registrar um usuário preenchendo o formulário e enviando.

e0d0b0f0c94120c2.png

Clique no link Pessoas registradas para verificar se o novo responsável pelo registro aparece na tabela.

ab3b980423d0c51.png

Parabéns, você terminou! Encerre o aplicativo pressionando CTRL+C na janela do terminal.

10. Limpeza

Para limpar seu ambiente, exclua o tópico do Pub/Sub e a instância do Cloud MySQL que você criou.

Como excluir a instância do Cloud MySQL

$ gcloud sql instances delete codelab-instance

Como excluir os recursos do Pub/Sub

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

11. Parabéns!

Você terminou de criar um aplicativo Spring Kotlin que se integra ao Cloud Pub/Sub e ao Cloud SQL (MySQL).

Saiba mais

Licença

Este conteúdo está sob a licença Atribuição 2.0 Genérica da Creative Commons.