Google Cloud Platform を使用して Kotlin Spring アプリケーションを構築する

1. はじめに

Spring Framework 5.0 には、Kotlin デベロッパーが Spring を簡単に使用できるように、専用の Kotlin サポートが追加されています。その結果、これらの変更により、Spring Cloud GCP が提供する Google Cloud 統合が Kotlin でもシームレスに機能するようになりました。この Codelab では、Kotlin アプリケーションで Google Cloud サービスを簡単に使い始めることができます。

この Codelab では、Kotlin でシンプルな登録アプリケーションを設定する手順について説明します。ここでは、Cloud Pub/SubCloud SQL などの GCP サービスを使用する方法を示します。

作成するアプリの概要

この Codelab では、登録者情報を受け取り、これを Cloud Pub/Sub トピックにパブリッシュして、Cloud MySQL データベースに永続化する Kotlin Spring Boot アプリケーションを設定します。

学習内容

Kotlin Spring アプリケーションで Google Cloud サービスと統合する方法。

必要なもの

  • Google Cloud Platform プロジェクト
  • ブラウザ(Chrome や Firefox など)

このチュートリアルをどのように使用しますか?

通読するのみ 通読し、演習を行う

HTML/CSS ウェブアプリの作成経験をどのように評価されますか。

初心者 中級者 上級者

Google Cloud Platform サービスのご利用経験についてどのように評価されますか?

<ph type="x-smartling-placeholder"></ph> 初心者 中級 上達 をご覧ください。

2. 設定と要件

セルフペース型の環境設定

  1. Cloud Console にログインし、新しいプロジェクトを作成するか、既存のプロジェクトを再利用します(Gmail アカウントまたは G Suite アカウントをお持ちでない場合は、アカウントを作成する必要があります)。

dMbN6g9RawQj_VXCSYpdYncY-DbaRzr2GbnwoV7jFf1u3avxJtmGPmKpMYgiaMH-qu80a_NJ9p2IIXFppYk8x3wyymZXavjglNLJJhuXieCem56H30hwXtd8PvXGpXJO9gEUDu3cZw

ci9Oe6PgnbNuSYlMyvbXF1JdQyiHoEgnhl4PlV_MFagm2ppzhueRkqX4eLjJllZco_2zCp0V0bpTupUSKji9KkQyWqj11pqit1K1faS1V6aFxLGQdkuzGp4rsQTan7F01iePL5DtqQ

8-tA_Lheyo8SscAVKrGii2coplQp2_D1Iosb2ViABY0UUO1A8cimXUu6Wf1R9zJIRExL5OB2j946aIiFtyKTzxDcNnuznmR45vZ2HMoK3o67jxuoUJCAnqvEX6NgPGFjCVNgASc-lg

プロジェクト ID を忘れないようにしてください。プロジェクト ID はすべての Google Cloud プロジェクトを通じて一意の名前にする必要があります(上記の名前はすでに使用されているので使用できません)。以降、このコードラボでは PROJECT_ID と呼びます。

  1. 次に、Google Cloud リソースを使用するために、Cloud Console で課金を有効にする必要があります。

このコードラボを実行しても、費用はほとんどかからないはずです。このチュートリアル以外で請求が発生しないように、リソースのシャットダウン方法を説明する「クリーンアップ」セクションの手順に従うようにしてください。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。

Google Cloud Shell

Google Cloud はノートパソコンからリモートで操作できますが、この Codelab では Cloud 上で動作するコマンドライン環境である Google Cloud Shell を使用します。

Cloud Shell をアクティブにする

  1. Cloud Console で、[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 GB の永続ホーム ディレクトリが用意されており、Google Cloud で稼働するため、ネットワーク パフォーマンスが充実しており認証もスムーズです。このコードラボでの作業のほとんどは、ブラウザまたは Chromebook から実行できます。

Cloud Shell に接続すると、すでに認証は完了しており、プロジェクトに各自のプロジェクト ID が設定されていることがわかります。

  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 を使用してリソースをプロビジョニングします。Google Cloud コンソールの [Cloud Pub/Sub] セクションから Pub/Sub リソースを設定することもできます。

Cloud Shell ターミナルで、まず Pub/Sub API を有効にします。

$ gcloud services enable pubsub.googleapis.com

次に、このアプリケーション用に registrations という名前の Pub/Sub トピックを作成します。アプリケーションで送信された登録情報は、このトピックに公開されます。

$ gcloud pubsub topics create registrations

最後に、トピックのサブスクリプションを作成します。Pub/Sub サブスクリプションを使用すると、トピックからメッセージを受信できます。

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

これで、アプリケーションの Cloud Pub/Sub トピックとサブスクリプションの作成が完了しました。

4. Cloud SQL(MySQL)インスタンスとデータベースを作成する

このサンプル アプリケーションでは、登録者情報を保持するデータベース インスタンスもセットアップする必要があります。この手順では、Cloud SQL リソースのプロビジョニングにも Cloud Shell ターミナルを使用します。Cloud SQL インスタンスは、Google Cloud コンソールからも表示、構成できます。

まず、Cloud SQL Admin API を有効にします。

$ 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>

次に、登録ユーザーを表示するための registrants.html という名前の Thymeleaf テンプレートを作成します。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. Pub/Sub トピックへの登録者の送信

このステップでは、ウェブフォームで送信された登録者を Cloud Pub/Sub トピックにパブリッシュする機能を実装します。

データクラスを追加する

まず、Kotlin データクラスをいくつか作成します。これらは Google の JPA エンティティとなり、フォームを介して送信された登録者の中間代表者としても機能します。

デモ パッケージに、Person クラスと Spring Data PersonRepository の 2 つの新しいファイルを追加します。この 2 つのクラスにより、Spring Data JPA を使用して MySQL データベースに登録エントリを簡単に保存および取得できるようになります。

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>

ウェブ コントローラを追加する

次に、フォームの登録者を処理して、先ほど作成した Cloud Pub/Sub トピックに情報を送信するコントローラ クラスを作成します。このコントローラは、次の 2 つのエンドポイントを作成します。

  • /registerPerson: 登録者情報が送信されて Pub/Sub トピックに送信される POST エンドポイント。registerPerson(..) 関数では、PubSubTemplate を使用して登録者情報が Pub/Sub トピックに送信されます。これは Spring Cloud GCP Pub/Sub 統合の便利なクラスで、Cloud Pub/Sub の操作を開始するために必要なボイラープレート コードを最小限に抑えます。
  • /registrants: データベースに正常に登録されたすべての登録者を表示します。この情報は、前のステップで作成した Spring Data リポジトリを使用して MySQL インスタンスから取得されます。

デモ パッケージに次のコントローラ クラスを作成します。

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 の追加

コントローラで、文字列ではなく Person オブジェクトを Pub/Sub トピックにパブリッシュしていることにお気づきでしょうか。これが可能になるのは、Spring Cloud GCP の Spring Cloud GCP サポートを利用してカスタム JSON ペイロードをトピックに送信するためです。このライブラリでは、オブジェクトを JSON にシリアル化し、JSON ペイロードをトピックに送信し、受信時にペイロードをシリアル化解除できます。

この機能を利用するには、アプリのコンテキストに ObjectMapper Bean を追加する必要があります。この ObjectMapper Bean は、アプリケーションがメッセージを送受信するときに、JSON との間でオブジェクトをシリアル化するために使用されます。DemoApplication.kt クラスで、JacksonPubSubMessageConverter Spring Bean を追加します。

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 を利用します。Pub/Sub トピックからメッセージを読み取り、さらなる処理のために pubsubInputChannel に配置できるように、PubSubInboundChannelAdapter を追加します。次に、pubsubInputChannel に到着したメッセージで呼び出されるように、@ServiceActivator を使用して messageReceiver 関数を構成します。

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

[Registered People] リンクをクリックして、新しい登録者が表に表示されることを確認します。

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. 完了

これで、Cloud Pub/Sub および Cloud SQL(MySQL)と統合する Spring Kotlin アプリケーションの作成が完了しました。

詳細

ライセンス

この作業はクリエイティブ・コモンズの表示 2.0 汎用ライセンスにより使用許諾されています。