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. Настройка и требования
Настройка среды для самостоятельного обучения
- Войдите в Cloud Console и создайте новый проект или используйте существующий. (Если у вас еще нет учетной записи Gmail или G Suite, вам необходимо ее создать .)
Запомните идентификатор проекта (Project ID) — уникальное имя для всех проектов Google Cloud (указанное выше имя уже занято и вам не подойдёт, извините!). В дальнейшем в этом практическом занятии оно будет обозначаться как PROJECT_ID .
- Далее вам потребуется включить оплату в Cloud Console, чтобы использовать ресурсы Google Cloud.
Выполнение этого практического задания не должно стоить дорого, если вообще что-либо. Обязательно следуйте инструкциям в разделе «Очистка», где указано, как отключить ресурсы, чтобы избежать дополнительных расходов после завершения этого урока. Новые пользователи Google Cloud имеют право на бесплатную пробную версию стоимостью 300 долларов США .
Google Cloud Shell
Хотя Google Cloud можно запускать удалённо с ноутбука, в этом практическом занятии мы будем использовать Google Cloud Shell — среду командной строки, работающую в облаке.
Активировать Cloud Shell
- В консоли Cloud нажмите «Активировать Cloud Shell» .
.
Если вы никогда раньше не запускали Cloud Shell, вам будет показан промежуточный экран (внизу), описывающий его назначение. В этом случае нажмите «Продолжить» (и вы больше никогда его не увидите). Вот как выглядит этот одноразовый экран:
Подготовка и подключение к Cloud Shell займут всего несколько минут.
Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Она предоставляет постоянный домашний каталог размером 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Большая часть, если не вся, работа в этом практическом задании может быть выполнена с помощью обычного браузера или вашего Chromebook.
После подключения к Cloud Shell вы увидите, что ваша аутентификация пройдена и что проект уже настроен на ваш идентификатор проекта.
- Выполните следующую команду в 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.

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 и убедитесь, что отображается главная страница. Однако никакие функции пользовательского интерфейса работать не будут, поскольку отсутствует веб-контроллер. Он будет добавлен на следующем шаге.

После предварительного просмотра приложения нажмите 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
Нажмите кнопку « Предварительный просмотр» еще раз и попробуйте зарегистрировать пользователя, заполнив форму и отправив ее.

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

Поздравляем, вы завершили работу! Закройте приложение, нажав 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).
Узнать больше
- Проект Spring на GCP: http://cloud.spring.io/spring-cloud-gcp/
- Репозиторий Spring на GCP на GitHub: https://github.com/GoogleCloudPlatform/spring-cloud-gcp
- Java на платформе Google Cloud: https://cloud.google.com/java/
- Примеры приложений Kotlin с использованием GCP: https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples
Лицензия
Данная работа распространяется под лицензией Creative Commons Attribution 2.0 Generic.