Создайте приложение 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
  • Браузер, например 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

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

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

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

Google Cloud Shell

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

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

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

zlNW0HehB_AFW1qZ4AyebSQUdWm95n7TbnOr7UVm3j9dFcg6oWapJRlC0jnU1Mvb-IQp-trP1Px8xKNwt6o3pP6fyih947sEhOFI4IRF0W7WZk6hFqZDUGXQQXrw21GuMm2ecH rbzQ

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

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

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

pTv5mEKzWMWp5VBrg2eGcuRPv9dLInPToS-mohlrqDASyYGWnZ_SwE-MzOWHe76ZdCSmw0kgWogSJv27lrQE8pvA5OD6P1I47nz8vrAdK7yR1NseZKJvcxAZrPb8wRxoqyTpD-gbha

Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Он предлагает постоянный домашний каталог объемом 5 ГБ и работает в Google Cloud, что значительно повышает производительность сети и аутентификацию. Большую часть, если не всю, работу в этой лаборатории кода можно выполнить с помощью просто браузера или 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. Предоставление ресурсов Pub/Sub

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

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

В терминале 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 Console .

Сначала включите 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:

регистрации-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, за исключением некоторых дополнительных элементов уценки для обработки динамического контента. Этот шаблон принимает один параметр с именем 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. Отправка зарегистрировавшихся в тему Pub/Sub

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

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

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

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

$ ./mvnw spring-boot:run

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

9. Чтение зарегистрированных пользователей из темы Pub/Sub

На последнем этапе мы обработаем информацию о регистранте из темы Pub/Sub и сохраним ее в базе данных Cloud 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.

Удаление экземпляра Cloud 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 License.