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 significam que as integrações do Google Cloud fornecidas pelo Spring Cloud GCP também funcionam perfeitamente em Kotlin. Neste codelab, você vai descobrir 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, incluindo: Cloud Pub/Sub e Cloud SQL.

O que você vai criar

Neste codelab, você vai configurar um aplicativo Kotlin Spring Boot que aceita informações de inscritos, publica essas informações em um tópico do Cloud Pub/Sub e as salva em um banco de dados do Cloud MySQL.

O que você vai aprender

Como integrar aos 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 em 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 em 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, vamos configurar um tópico e uma assinatura do Cloud Pub/Sub. Neste aplicativo, vamos publicar informações de registro em um tópico do Pub/Sub. Depois, as informações serão lidas desse tópico e persistidas em um banco de dados.

Neste tutorial, vamos usar 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 esse aplicativo. As informações de registro enviadas pelo aplicativo serão publicadas neste tópico.

$ gcloud pubsub topics create registrations

Por fim, crie uma assinatura para o tópico. Com uma assinatura do Pub/Sub, você pode receber mensagens de um tópico.

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

Você concluiu a criação de 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 aplicativo de amostra, também precisamos configurar uma instância de banco de dados para armazenar as informações do registrante. Esta etapa também vai usar o terminal do Cloud Shell para provisionar recursos do Cloud SQL. Também é possível acessar e configurar as 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, vamos provisionar 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 banco de dados chamado registrants.

$ 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 vão continuar usando o Cloud Shell descrito nas etapas de configuração.

Primeiro, vamos usar o Initializr para gerar o código de scaffolding do 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 o código no ambiente shell 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 precisa permitir que você modifique diretamente os arquivos do projeto produzidos pelo Initialzr.

cce293b40119c37b.png

6. Configuração do banco de dados

Primeiro, configure o aplicativo para que ele possa se conectar ao banco de dados do Cloud MySQL que você configurou. As bibliotecas do Spring Cloud GCP oferecem um iniciador 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, é necessário modificar o arquivo de configuração application.properties para descrever a configuração do banco de dados. Copie as seguintes propriedades no arquivo application.properties.

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

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

A saída disso 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. O aplicativo precisa ter um formulário para registrar pessoas e uma visualização que mostre todos os inscritos.

Para a página inicial, crie um index.html que contenha o formulário de inscrição.

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, vamos criar um modelo Thymeleaf chamado registrants.html para mostrar os usuários registrados. O Thymeleaf é uma estrutura de modelos que usamos para construir e veicular HTML criado dinamicamente. O modelo parece HTML, mas tem alguns elementos de markdown extras para lidar com conteúdo dinâmico. Esse modelo aceita um único parâmetro chamado personsList, que contém todos os inscritos pelo aplicativo.

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, você pode verificar se o conteúdo estático está sendo veiculado.

Crie e execute o app usando o Maven:

$ ./mvnw spring-boot:run

Clique no botão de visualização na janela do Cloud Shell e verifique se a página inicial está sendo renderizada. No entanto, nenhuma funcionalidade da interface vai funcionar porque não temos um controlador da Web. Isso será adicionado na próxima etapa.

5e38bb0d0e93002e.png

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

8. Enviar os inscritos para um tópico do Pub/Sub

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

Adicionar as classes de dados

Primeiro, vamos criar algumas classes de dados Kotlin. Elas serão nossas entidades JPA e também vão atuar como nossa representação intermediária dos inscritos enviados pelo formulário.

No pacote de demonstração, adicione dois novos arquivos: uma classe Person e um PersonRepository do Spring Data. Essas duas classes vão permitir armazenar e recuperar entradas de registro do nosso banco de dados MySQL usando o Spring Data JPA com facilidade.

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 Controller 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 registrante são enviadas e, em seguida, enviadas ao tópico do Pub/Sub. Na função registerPerson(..), as informações do responsável pelo registro são enviadas ao tópico do Pub/Sub usando PubSubTemplate, uma classe de conveniência das integrações do Pub/Sub do Spring Cloud GCP, que minimiza o código boilerplate necessário para começar a interagir com o Cloud Pub/Sub.
  • /registrants: mostra todos os inscritos registrados com sucesso no banco de dados. Essas informações são recuperadas da instância do MySQL usando o repositório do 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 registrante enviadas pelo formulário da Web e as publica no tópico do Pub/Sub.

Adicionar o bean do ObjectMapper JSON

Você deve ter 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 payloads JSON personalizados a serem enviados aos tópicos. As bibliotecas permitem serializar objetos em JSON, enviar payloads JSON a um tópico e desserializar o payload quando ele é recebido.

Para aproveitar esse recurso, precisamos adicionar um bean ObjectMapper ao contexto do aplicativo. Esse bean ObjectMapper será usado para serializar objetos de e para JSON quando seu aplicativo enviar e receber mensagens. Na classe DemoApplication.kt, adicione o bean JacksonPubSubMessageConverter do Spring:

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 com o comando:

$ ./mvnw spring-boot:run

No formulário da Web na página principal, o aplicativo vai 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. Leitura de inscritos do tópico do Pub/Sub

Na etapa final, vamos processar as informações do registrante do tópico do Pub/Sub e manter as informações no banco de dados do Cloud MySQL. Isso vai concluir o aplicativo, permitindo que você envie novos inscritos pelo formulário e veja todos os usuários registrados pelo endpoint /registrants.

Esse aplicativo vai aproveitar a Spring Integration, que oferece muitas abstrações convenientes para lidar com mensagens. Vamos adicionar um PubSubInboundChannelAdapter para ler mensagens do tópico do Pub/Sub e colocá-las no pubsubInputChannel para processamento posterior. Em seguida, vamos configurar a função messageReceiver usando @ServiceActivator para ser invocada com as mensagens que chegam no 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 Prévia novamente e tente registrar um usuário preenchendo e enviando o formulário.

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! Pressione CTRL+C na janela do terminal para encerrar o aplicativo.

10. Limpeza

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

Excluir a instância do Cloud MySQL

$ gcloud sql instances delete codelab-instance

Excluir os recursos do Pub/Sub

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

11. Parabéns!

Você concluiu a criação de 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.