1. מבוא
ב-Spring Framework 5.0 נוסף תמיכה ייעודית ב-Kotlin, כך שמפתחי Kotlin יכולים להשתמש ב-Spring בקלות. כתוצאה מכך, השינויים האלה מאפשרים גם לשילובי Google Cloud שמוצעים על ידי Spring Cloud GCP לפעול בצורה חלקה ב-Kotlin. ב-Codelab הזה תלמדו כמה קל להתחיל להשתמש בשירותי Google Cloud באפליקציות Kotlin.
ב-Codelab הזה תלמדו איך להגדיר ב-Kotlin אפליקציית רישום פשוטה שממחישה את השימוש בשירותי GCP, כולל Cloud Pub/Sub ו-Cloud SQL.
מה תפַתחו
ב-codelab הזה תגדירו אפליקציית 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, אתם צריכים ליצור חשבון).
חשוב לזכור את מזהה הפרויקט, שהוא שם ייחודי בכל הפרויקטים ב-Google Cloud (השם שלמעלה כבר תפוס ולא יתאים לכם, מצטערים!). בהמשך ה-codelab הזה נתייחס אליו כאל PROJECT_ID.
- לאחר מכן, תצטרכו להפעיל את החיוב ב-Cloud Console כדי להשתמש במשאבים של Google Cloud.
העלות של התרגול הזה לא אמורה להיות גבוהה, ואולי אפילו לא תצטרכו לשלם בכלל. חשוב לפעול לפי ההוראות בקטע 'ניקוי' כדי להשבית את המשאבים, וכך לא תחויבו אחרי שתסיימו את המדריך הזה. משתמשים חדשים ב-Google Cloud זכאים לתוכנית תקופת ניסיון בחינם בשווי 300$.
Google Cloud Shell
אפשר להפעיל את Google Cloud מרחוק מהמחשב הנייד, אבל ב-codelab הזה נשתמש ב-Google Cloud Shell, סביבת שורת פקודה שפועלת בענן.
הפעלת Cloud Shell
- ב-Cloud Console, לוחצים על Activate Cloud Shell
.
אם זו הפעם הראשונה שאתם מפעילים את Cloud Shell, יוצג לכם מסך ביניים (בחלק הנגלל) עם תיאור של Cloud Shell. במקרה כזה, לוחצים על המשך (והמסך הזה לא יוצג לכם יותר). כך נראה המסך החד-פעמי:
הקצאת המשאבים והחיבור ל-Cloud Shell נמשכים רק כמה רגעים.
המכונה הווירטואלית הזו כוללת את כל הכלים שדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את הביצועים והאימות ברשת. אפשר לבצע את רוב העבודה ב-codelab הזה, אם לא את כולה, באמצעות דפדפן או 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. הקצאת משאבים ב-Pub/Sub
קודם כול, צריך להגדיר נושא ומינוי ב-Cloud Pub/Sub. באפליקציה הזו, נפרסם פרטי רישום לנושא ב-Pub/Sub. לאחר מכן, המידע ייקרא מהנושא הזה ויישמר במסד נתונים.
במדריך הזה נסתמך על Cloud Shell כדי להקצות את המשאבים שלנו. שימו לב שאפשר גם להגדיר משאבי Pub/Sub דרך הקטע Cloud Pub/Sub במסוף Google Cloud.
במסוף Cloud Shell, מפעילים קודם את Pub/Sub API.
$ 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.
קודם צריך להפעיל את 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 כדי ליצור את קוד ה-scaffolding של הפרויקט. בחלון 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 וגם קוד scaffolding לאפליקציה בספרייה registrations-codelab/. בקטעים הבאים מתוארות עריכות הקוד שצריך לבצע כדי ליצור אפליקציה תקינה.
עורך הקוד של Cloud Shell
הדרך הקלה ביותר להתחיל לשנות ולהציג קוד בסביבת Cloud Shell היא באמצעות עורך הקוד המובנה של Cloud Shell.
אחרי שפותחים מופע של Cloud Shell, לוחצים על סמל העיפרון כדי לפתוח את עורך הקוד. העורך צריך לאפשר לכם לשנות ישירות את קובצי הפרויקט שנוצרו על ידי Initializr.

6. הגדרת מסד נתונים
קודם כל, מגדירים את האפליקציה כך שתוכל להתחבר למסד הנתונים של Cloud MySQL שהגדרתם. ספריות Spring Cloud GCP מציעות Cloud MySQL starter, שכולל את התלות הנדרשת לחיבור למכונת 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 הוא framework ליצירת תבניות שמשמש אותנו ליצירה ולהצגה של 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. שתי המחלקות האלה יאפשרו לנו לאחסן ולאחזר בקלות רשומות הרשמה ממסד הנתונים של 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 שמצמצמת את קוד ה-boilerplate שנדרש כדי להתחיל אינטראקציה עם Cloud Pub/Sub. /registrants: הצגת כל הנרשמים שנרשמו בהצלחה במסד הנתונים. המידע הזה מאוחזר ממופע MySQL באמצעות מאגר Spring Data שיצרנו בשלב הקודם.
יוצרים את המחלקה Controller הבאה בחבילת ההדגמה:
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.
הוספת Bean של מיפוי אובייקט JSON
יכול להיות ששמתם לב שב-Controller אנחנו מפרסמים אובייקט Person בנושא Pub/Sub ולא מחרוזת. הדבר אפשרי כי אנחנו משתמשים בתמיכה של Spring Cloud GCP במטענים ייעודיים (payload) מותאמים אישית של JSON שנשלחים לנושאים – הספריות מאפשרות לסדר אובייקטים בפורמט JSON, לשלוח מטענים ייעודיים (payload) של JSON לנושא ולבטל את הסדר של המטען הייעודי (payload) כשהוא מתקבל.
כדי להשתמש בתכונה הזו, אנחנו צריכים להוסיף ObjectMapper bean להקשר של האפליקציה. רכיב ה-ObjectMapper הזה ישמש לסריאליזציה של אובייקטים ל-JSON ומ-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
בשלב האחרון, נעבד את פרטי הנרשם מנושא 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
לוחצים שוב על הלחצן תצוגה מקדימה, ממלאים את הטופס ושולחים אותו כדי לנסות לרשום משתמש.

לוחצים על הקישור אנשים שנרשמו כדי לוודא שהרושם החדש מופיע בטבלה.

סיימתם. מפסיקים את האפליקציה על ידי הקשה על 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).
מידע נוסף
- פרויקט Spring ב-GCP: http://cloud.spring.io/spring-cloud-gcp/
- מאגר GitHub של Spring ב-GCP: https://github.com/GoogleCloudPlatform/spring-cloud-gcp
- Java ב-Google Cloud Platform: https://cloud.google.com/java/
- אפליקציות לדוגמה ב-Kotlin באמצעות GCP: https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples
רישיון
עבודה זו מורשית תחת רישיון Creative Commons שמותנה בייחוס 2.0 כללי.