1. 簡介
Spring Framework 5.0 新增了對 Kotlin 的特別支援,讓 Kotlin 開發人員更容易使用 Spring。這些異動的緣故,讓 Spring Cloud GCP 提供的 Google Cloud 整合功能也能在 Kotlin 中完美運作。在這個程式碼研究室中,您將瞭解如何輕鬆在 Kotlin 應用程式中使用 Google Cloud 服務!
這個程式碼研究室會逐步引導您使用 Kotlin 設定簡單的註冊應用程式,示範如何使用 Cloud Pub/Sub 和 Cloud SQL 等 GCP 服務。
建構項目
在本程式碼研究室中,您將設定 Kotlin Spring Boot 應用程式,接受註冊者資訊、將這項資訊發布至 Cloud Pub/Sub 主題,並保留至 Cloud MySQL 資料庫。
課程內容
如何在 Kotlin Spring 應用程式中整合 Google Cloud 服務。
軟硬體需求
- Google Cloud Platform 專案
- 瀏覽器,例如 Chrome 或 Firefox
您會如何使用這個教學課程?
針對建立 HTML/CSS 網頁應用程式的經驗,您會給予什麼評價?
根據您使用 Google Cloud Platform 服務的經驗,您會給予什麼評價?
2. 設定和需求
自修環境設定
提醒您,專案 ID 是所有 Google Cloud 專案的專屬名稱 (已經有人使用上述名稱,很抱歉對您不符!)。稍後在本程式碼研究室中會稱為 PROJECT_ID
。
- 接下來,您需要在 Cloud 控制台中啟用計費功能,才能使用 Google Cloud 資源。
執行這個程式碼研究室並不會產生任何費用,如果有的話。請務必依照「清除所用資源」一節指示本節將說明如何關閉資源,這樣您就不會產生本教學課程結束後產生的費用。Google Cloud 的新使用者符合 $300 美元免費試用計畫的資格。
Google Cloud Shell
雖然 Google Cloud 可以在筆電上遠端操作,但在本程式碼研究室中,我們會使用 Google Cloud Shell,這是 Cloud 中運作的指令列環境。
啟用 Cloud Shell
- 在 Cloud 控制台中,按一下「啟用 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 Shell 終端機來佈建 Cloud SQL 資源。請注意,您也可以透過 Google Cloud 控制台查看及設定 Cloud SQL 執行個體。
首先,請啟用 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
這個指令會在 registrations-codelab/
目錄中產生初始 Maven 專案設定,以及應用程式的鷹架程式碼。以下各節說明製作正常運作的應用程式所需的程式碼編輯功能。
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 資料類別。這些實體將是我們的 JPA 實體,也是透過表單提交的註冊者中間代表。
在示範套件中新增兩個檔案:Person
類別和 Spring Data PersonRepository
。透過這兩個類別,我們可使用 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 主題。這個控制器會建立兩個端點:
/registerPerson
:提交註冊者資訊並傳送至 Pub/Sub 主題的 POST 端點。在registerPerson(..)
函式中,系統會使用PubSubTemplate
將註冊者資訊傳送至 Pub/Sub 主題,這是 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 物件 Mapper Bean
您可能在控制器中發現,我們將 Person
物件發布至 Pub/Sub 主題,而非字串。之所以會這樣,是因為我們利用 Spring Cloud GCP 支援將自訂 JSON 酬載傳送至主題,而程式庫可讓您將物件序列化到 JSON、將 JSON 酬載傳送至主題,並在收到酬載時取消序列化。
為了利用此功能,我們必須在應用程式結構定義中加入 ObjectMapper
Bean。這個 ObjectMapper
值會在應用程式傳送及接收訊息時,用來將 JSON 傳入或傳出 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,提供許多方便處理訊息的抽象化機制。我們將新增 PubSubInboundChannelAdapter
,以便讀取來自 Pub/Sub 主題的訊息,並將其放在 pubsubInputChannel
中以進行後續處理。接下來,我們會使用 @ServiceActivator
設定 messageReceiver
函式,以便與傳入 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
再次點選「預覽」按鈕,然後試著填寫表單並提交,藉此註冊使用者。
按一下「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 應用程式。
瞭解詳情
- Spring on GCP 專案:http://cloud.spring.io/spring-cloud-gcp/
- GCP GitHub 存放區的 Spring: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
授權
這項內容採用的是創用 CC 姓名標示 2.0 通用授權。