Создайте приложение Kotlin Spring с помощью Google Cloud Platform

1. Введение

В Spring Framework 5.0 была добавлена ​​специальная поддержка Kotlin, что значительно упростило использование Spring для разработчиков на Kotlin. В результате эти изменения позволили беспрепятственно использовать интеграции с Google Cloud, предоставляемые Spring Cloud GCP, и в Kotlin. В этом практическом занятии вы увидите, как легко начать использовать сервисы Google Cloud в ваших приложениях на Kotlin!

В этом практическом занятии рассматривается настройка простого приложения для регистрации на Kotlin с использованием сервисов GCP, включая Cloud Pub/Sub и Cloud SQL .

Что вы построите

В этом практическом занятии вы настроите приложение Kotlin Spring Boot, которое будет принимать информацию о зарегистрированных пользователях, публиковать её в топик Cloud Pub/Sub и сохранять в базу данных Cloud MySQL.

Что вы узнаете

Как интегрировать сервисы Google Cloud в ваше приложение Kotlin Spring.

Что вам понадобится

  • Проект Google Cloud Platform
  • Браузер, например Chrome или Firefox.

Как вы будете использовать этот учебник?

Прочитайте только от начала до конца. Прочитайте текст и выполните упражнения.

Как бы вы оценили свой опыт разработки веб-приложений на HTML/CSS?

Новичок Средний Профессионал

Как бы вы оценили свой опыт использования сервисов Google Cloud Platform?

Новичок Средний Профессионал

2. Настройка и требования

Настройка среды для самостоятельного обучения

  1. Войдите в Cloud Console и создайте новый проект или используйте существующий. (Если у вас еще нет учетной записи Gmail или G Suite, вам необходимо ее создать .)

dMbN6g9RawQj_VXCSYpdYncY-DbaRzr2GbnwoV7jFf1u3avxJtmGPmKpMYgiaMH-qu80a_NJ9p2IIXFppYk8x3wyymZXavjglNLJJhuXieCem56H30hwXtd8PvXGpXJO9gEUDu3cZw

ci9Oe6PgnbNuSYlMyvbXF1JdQyiHoEgnhl4PlV_MFagm2ppzhueRkqX4eLjJllZco_2zCp0V0bpTupUSKji9KkQyWqj11pqit1K1faS1V6aFxLGQdkuzGp4rsQTan7F01iePL5DtqQ

8-tA_Lheyo8SscAVKrGii2coplQp2_D1Iosb2ViABY0UUO1A8cimXUu6Wf1R9zJIRExL5OB2j946aIiFtyKTzxDcNnuznmR45vZ2HMoK3o67jxuoUJCAnqvEX6NgPGFjCVNgASc-lg

Запомните идентификатор проекта (Project ID) — уникальное имя для всех проектов Google Cloud (указанное выше имя уже занято и вам не подойдёт, извините!). В дальнейшем в этом практическом занятии оно будет обозначаться как PROJECT_ID .

  1. Далее вам потребуется включить оплату в Cloud Console, чтобы использовать ресурсы Google Cloud.

Выполнение этого практического задания не должно стоить дорого, если вообще что-либо. Обязательно следуйте инструкциям в разделе «Очистка», где указано, как отключить ресурсы, чтобы избежать дополнительных расходов после завершения этого урока. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .

Google Cloud Shell

Хотя Google Cloud можно запускать удалённо с ноутбука, в этом практическом занятии мы будем использовать Google Cloud Shell — среду командной строки, работающую в облаке.

Активировать Cloud Shell

  1. В консоли Cloud нажмите «Активировать Cloud Shell» . H7JlbhKGHITmsxhQIcLwoe5HXZMhDlYue4K-SPszMxUxDjIeWfOHBfxDHYpmLQTzUmQ7Xx8o6OJUlANnQF0iBuUyfp1RzVad_4nCa0Zz5LtwBlUZFXFCWFrmrWZLqg1MkZz2LdgUDQ .

zlNW0HehB_AFW1qZ4AyebSQUdWm95n7TbnOr7UVm3j9dFcg6oWApJRlC0jnU1Mvb-IQp-trP1Px8xKNwt6o3pP6fyih947sEhOFI4IRF0W7WZk6hFqZDUGXQQXrw21GuMm2ecHrbzQ

Если вы никогда раньше не запускали Cloud Shell, вам будет показан промежуточный экран (внизу), описывающий его назначение. В этом случае нажмите «Продолжить» (и вы больше никогда его не увидите). Вот как выглядит этот одноразовый экран:

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

Подготовка и подключение к Cloud Shell займут всего несколько минут.

pTv5mEKzWMWp5VBrg2eGcuRPv9dLInPToS-mohlrqDASyYGWnZ_SwE-MzOWHe76ZdCSmw0kgWogSJv27lrQE8pvA5OD6P1I47nz8vrAdK7yR1NseZKJvcxAZrPb8wRxoqyTpD-gbhA

Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Она предоставляет постоянный домашний каталог размером 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Большая часть, если не вся, работа в этом практическом задании может быть выполнена с помощью обычного браузера или вашего Chromebook.

После подключения к Cloud Shell вы увидите, что ваша аутентификация пройдена и что проект уже настроен на ваш идентификатор проекта.

  1. Выполните следующую команду в Cloud Shell, чтобы подтвердить свою аутентификацию:
gcloud auth list

вывод команды

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

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

вывод команды

[core]
project = <PROJECT_ID>

Если это не так, вы можете установить это с помощью следующей команды:

gcloud config set project <PROJECT_ID>

вывод команды

Updated property [core/project].

3. Предоставление ресурсов для публикации/подписки.

Для начала нам потребуется настроить тему Cloud Pub/Sub и подписку. В этом приложении мы будем публиковать информацию о регистрации в тему Pub/Sub; затем эта информация считывается из темы и сохраняется в базе данных.

В этом руководстве мы будем использовать Cloud Shell для выделения ресурсов. Обратите внимание, что ресурсы Pub/Sub также можно настроить через раздел Cloud Pub/Sub в консоли Google Cloud.

В терминале Cloud Shell сначала включите API Pub/Sub.

$ gcloud services enable pubsub.googleapis.com

Далее мы создадим для этого приложения тему Pub/Sub с именем registrations . Информация о регистрации, предоставленная через приложение, будет опубликована в этой теме.

$ gcloud pubsub topics create registrations

Наконец, создайте подписку на тему. Подписка типа Pub/Sub позволяет получать сообщения из определенной темы.

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

Вы завершили создание темы и подписки Cloud Pub/Sub для вашего приложения.

4. Создайте экземпляр Cloud SQL (MySQL) и базу данных.

Для нашего тестового приложения нам также необходимо настроить экземпляр базы данных для хранения информации о регистрантах. На этом этапе также потребуется терминал Cloud Shell для выделения ресурсов Cloud SQL. Обратите внимание, что вы также можете просматривать и настраивать свои экземпляры Cloud SQL через консоль Google Cloud .

Сначала включите API администрирования Cloud SQL.

$ gcloud services enable sqladmin.googleapis.com

Далее мы создадим экземпляр Cloud SQL (MySQL). Выполнение этой команды может занять некоторое время.

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

После успешного создания экземпляра Cloud SQL создайте в нем новую базу данных с именем registrants .

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

Настройка экземпляра Cloud SQL и базы данных для вашего приложения завершена.

5. Инициализация приложения Spring Boot.

Теперь мы готовы приступить к написанию приложения. Следующие шаги будут продолжаться с использованием Cloud Shell, описанного в шагах по настройке.

Сначала мы воспользуемся Initializr для генерации кода-шаблона проекта. В окне Cloud Shell выполните следующую команду:

$ 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

Эта команда генерирует начальную настройку проекта Maven, а также шаблонный код для вашего приложения в каталоге registrations-codelab/ . В следующих разделах описаны изменения кода, необходимые для создания работающего приложения.

Редактор кода Cloud Shell

Самый простой способ начать изменять и просматривать код в среде Cloud Shell — использовать встроенный редактор кода Cloud Shell .

После запуска экземпляра Cloud Shell нажмите на значок карандаша , чтобы открыть редактор кода. Редактор позволит вам напрямую изменять файлы проекта, созданные Initialzr.

cce293b40119c37b.png

6. Конфигурация базы данных

Сначала настройте ваше приложение так, чтобы оно могло подключаться к созданной вами базе данных Cloud MySQL. Библиотеки Spring Cloud GCP предоставляют стартовый пакет Cloud MySQL , который содержит необходимые зависимости для подключения к экземпляру Cloud MySQL.

Добавьте зависимость spring-cloud-gcp-starter-sql-mysql в файл 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>

Кроме того, вам необходимо изменить конфигурационный файл application.properties , чтобы описать конфигурацию вашей базы данных. Скопируйте следующие свойства в файл application.properties .

Найдите имя подключения экземпляра к вашей базе данных:

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

Результаты этой операции будут использованы в файле application.properties для настройки информации о подключении.

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

Единственное свойство, которое необходимо изменить, — это имя подключения к экземпляру . Это значение должно быть отформатировано как число, разделенное двоеточиями, в формате: YOUR_GCP_PROJECT_ID:REGION:DATABASE_INSTANCE_NAME .

7. Создание статического контента

Сначала мы создадим фронтенд для нашего приложения. Приложение должно содержать форму для регистрации пользователей, а также окно, отображающее всех успешно зарегистрировавшихся.

Для главной страницы создайте файл index.html , содержащий форму регистрации.

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>

Далее мы создадим шаблон Thymeleaf с именем registrants.html для отображения зарегистрированных пользователей. Thymeleaf — это фреймворк для создания шаблонов, который мы используем для построения и отображения динамически создаваемого HTML-кода. Вы увидите, что шаблон выглядит как HTML, за исключением того, что в нем есть несколько дополнительных элементов Markdown для обработки динамического контента. Этот шаблон принимает один параметр под названием personsList , который содержит всех зарегистрированных пользователей, прошедших регистрацию через приложение.

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>

На этом этапе вы можете убедиться, что статический контент отображается.

Соберите и запустите приложение с помощью Maven:

$ ./mvnw spring-boot:run

Нажмите на кнопку предварительного просмотра в окне Cloud Shell и убедитесь, что отображается главная страница. Однако никакие функции пользовательского интерфейса работать не будут, поскольку отсутствует веб-контроллер. Он будет добавлен на следующем шаге.

5e38bb0d0e93002e.png

После предварительного просмотра приложения нажмите CTRL+C , чтобы закрыть его.

8. Направление зарегистрированных участников на обсуждение в рамках тематического раздела/подраздела.

На этом этапе мы реализуем функцию, позволяющую публиковать данные участников, зарегистрировавшихся через веб-форму, в тему Cloud Pub/Sub.

Добавьте классы данных

Сначала мы создадим несколько классов данных Kotlin; они будут нашими сущностями JPA и также будут служить промежуточным представлением данных участников, предоставленных через форму.

В демонстрационный пакет добавьте два новых файла: класс Person и Spring Data PersonRepository . Эти два класса позволят нам легко хранить и извлекать записи о регистрации из нашей базы данных MySQL с помощью 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>

Добавьте веб-контроллер

Далее мы создадим класс Controller, который будет обрабатывать данные о зарегистрированных пользователях из формы и отправлять информацию в созданную вами ранее тему Cloud Pub/Sub. Этот контроллер создаст две конечные точки:

  • /registerPerson : POST-запрос, через который отправляется информация о регистранте в топик Pub/Sub. В функции registerPerson(..) информация о регистранте отправляется в топик Pub/Sub с помощью PubSubTemplate , удобного класса из интеграции Spring Cloud GCP Pub/Sub , который минимизирует шаблонный код, необходимый для начала взаимодействия с Cloud Pub/Sub.
  • /registrants : Отображает всех участников, успешно зарегистрированных в базе данных. Эта информация извлекается из экземпляра MySQL с помощью репозитория Spring Data, созданного на предыдущем шаге.

Создайте следующий класс контроллера в демонстрационном пакете:

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

Контроллер считывает информацию о зарегистрированном пользователе, предоставленную через веб-форму, а затем публикует эту информацию в теме Pub/Sub.

Добавление компонента JSON Object Mapper Bean

Возможно, вы заметили в контроллере, что мы публикуем в топик Pub/Sub объект Person , а не строку. Это возможно благодаря поддержке Spring Cloud GCP для отправки пользовательских JSON-данных в топики — библиотеки позволяют сериализовать объекты в JSON , отправлять JSON-данные в топик и десериализовать полученные данные.

Чтобы воспользоваться этой функцией, необходимо добавить в контекст приложения bean-компонент ObjectMapper . Этот bean-компонент ObjectMapper будет использоваться для сериализации объектов в JSON и обратно при отправке и получении сообщений вашим приложением. В классе DemoApplication.kt добавьте 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)
}

На этом этапе вы можете попробовать запустить приложение еще раз, выполнив следующую команду:

$ ./mvnw spring-boot:run

Теперь приложение отправит информацию из веб-формы на главной странице в созданную вами тему Pub/Sub. Однако пока это ничего полезного не делает, потому что нам еще нужно прочитать данные из этой темы Pub/Sub! Это будет сделано на следующем шаге.

9. Ознакомление с заявками участников из раздела "Публикация/Подтема".

На заключительном этапе мы обработаем информацию о зарегистрированных пользователях из топика Pub/Sub и сохраним её в облачную базу данных MySQL. Это завершит работу приложения, позволяя вам добавлять новых зарегистрированных пользователей через форму и просматривать всех зарегистрированных пользователей через конечную точку /registrants .

В этом приложении будет использоваться Spring Integration , который предлагает множество удобных абстракций для работы с обменом сообщениями. Мы добавим PubSubInboundChannelAdapter , чтобы иметь возможность читать сообщения из топика Pub/Sub и помещать их в pubsubInputChannel для дальнейшей обработки. Затем мы настроим функцию messageReceiver с помощью @ServiceActivator , чтобы она вызывалась при поступлении сообщений в 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)
}

На этом этапе настройка приложения завершена. Чтобы убедиться в корректной работе приложения, выполните следующую команду:

$ ./mvnw spring-boot:run

Нажмите кнопку « Предварительный просмотр» еще раз и попробуйте зарегистрировать пользователя, заполнив форму и отправив ее.

e0d0b0f0c94120c2.png

Нажмите на ссылку «Зарегистрированные лица» , чтобы убедиться, что новый зарегистрированный пользователь отображается в таблице.

ab3b980423d0c51.png

Поздравляем, вы завершили работу! Закройте приложение, нажав CTRL+C в окне терминала.

10. Уборка

Для очистки среды необходимо удалить созданные вами топик Pub/Sub и экземпляр Cloud MySQL.

Удаление облачного экземпляра MySQL

$ gcloud sql instances delete codelab-instance

Удаление ресурсов Pub/Sub

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

11. Поздравляем!

Вы завершили разработку приложения Spring Kotlin, интегрирующегося с Cloud Pub/Sub и Cloud SQL (MySQL).

Узнать больше

Лицензия

Данная работа распространяется под лицензией Creative Commons Attribution 2.0 Generic.