1. はじめに
Spring Framework 5.0 には、Kotlin デベロッパーが Spring を簡単に使用できるように、専用の Kotlin サポートが追加されています。その結果、これらの変更により、Spring Cloud GCP が提供する Google Cloud 統合が Kotlin でもシームレスに機能するようになりました。この Codelab では、Kotlin アプリケーションで Google Cloud サービスを簡単に使い始めることができます。
この Codelab では、Kotlin でシンプルな登録アプリケーションを設定する手順について説明します。ここでは、Cloud Pub/Sub や Cloud 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">2. 設定と要件
セルフペース型の環境設定
- Cloud Console にログインし、新しいプロジェクトを作成するか、既存のプロジェクトを再利用します(Gmail アカウントまたは G Suite アカウントをお持ちでない場合は、アカウントを作成する必要があります)。
プロジェクト ID を忘れないようにしてください。プロジェクト ID はすべての Google Cloud プロジェクトを通じて一意の名前にする必要があります(上記の名前はすでに使用されているので使用できません)。以降、このコードラボでは PROJECT_ID
と呼びます。
- 次に、Google Cloud リソースを使用するために、Cloud Console で課金を有効にする必要があります。
このコードラボを実行しても、費用はほとんどかからないはずです。このチュートリアル以外で請求が発生しないように、リソースのシャットダウン方法を説明する「クリーンアップ」セクションの手順に従うようにしてください。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。
Google Cloud Shell
Google Cloud はノートパソコンからリモートで操作できますが、この Codelab では Cloud 上で動作するコマンドライン環境である Google Cloud Shell を使用します。
Cloud Shell をアクティブにする
- Cloud Console で、[Cloud Shell をアクティブにする] をクリックします。
Cloud Shell を起動したことがない場合、その内容を説明する中間画面が(スクロールしなければ見えない範囲に)が表示されます。その場合は、[続行] をクリックします(以後表示されなくなります)。このワンタイム スクリーンは次のようになります。
Cloud Shell のプロビジョニングと接続に少し時間がかかる程度です。
この仮想マシンには、必要な開発ツールがすべて準備されています。5 GB の永続ホーム ディレクトリが用意されており、Google Cloud で稼働するため、ネットワーク パフォーマンスが充実しており認証もスムーズです。このコードラボでの作業のほとんどは、ブラウザまたは Chromebook から実行できます。
Cloud Shell に接続すると、すでに認証は完了しており、プロジェクトに各自のプロジェクト ID が設定されていることがわかります。
- 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 が生成したプロジェクト ファイルを直接変更できます。
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 ウィンドウでプレビュー ボタンをクリックして、ホームページがレンダリングされることを確認します。ウェブ コントローラがないため、管理画面のどの機能も動作しません。これは次のステップで追加します。
アプリのプレビューを確認したら、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
[プレビュー] ボタンを再度クリックし、フォームに記入して送信し、ユーザーを登録します。
[Registered People] リンクをクリックして、新しい登録者が表に表示されることを確認します。
お疲れさまでした。これで完了です。ターミナル ウィンドウで 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 アプリケーションの作成が完了しました。
詳細
- GCP プロジェクトの Spring: http://cloud.spring.io/spring-cloud-gcp/
- GCP の Spring の GitHub リポジトリ: https://github.com/GoogleCloudPlatform/spring-cloud-gcp
- Google Cloud Platform での Java: https://cloud.google.com/java/
- GCP を使用した Kotlin アプリケーションのサンプル: https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples
ライセンス
この作業はクリエイティブ・コモンズの表示 2.0 汎用ライセンスにより使用許諾されています。