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 USD.
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>
הוספת בקר אינטרנט
לאחר מכן, ניצור מחלקה של בקר שתעבד את הנרשמים מהטופס ותשלח את המידע לנושא 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 on 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.