1. 소개
Spring Framework 5.0에는 Kotlin 개발자가 Spring을 쉽게 사용할 수 있도록 전용 Kotlin 지원이 추가되었습니다. 이러한 변경 덕분에 Spring Cloud GCP에서 제공하는 Google Cloud 통합 역시 Kotlin에서도 원활하게 작동합니다. 이 Codelab에서는 Kotlin 애플리케이션에서 Google Cloud 서비스를 사용하기가 얼마나 쉬운지 확인합니다.
이 Codelab에서는 Cloud Pub/Sub, Cloud SQL 등의 GCP 서비스를 사용하는 방법을 보여주는 Kotlin으로 간단한 등록 애플리케이션을 설정하는 방법을 안내합니다.
빌드할 항목
이 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 계정이 없으면 만들어야 합니다.)
모든 Google Cloud 프로젝트에서 고유한 이름인 프로젝트 ID를 기억하세요(위의 이름은 이미 사용되었으므로 사용할 수 없습니다). 이 ID는 나중에 이 Codelab에서 PROJECT_ID
라고 부릅니다.
- 그런 후 Google Cloud 리소스를 사용할 수 있도록 Cloud Console에서 결제를 사용 설정해야 합니다.
이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 가이드를 마친 후 비용이 결제되지 않도록 리소스 종료 방법을 알려주는 '삭제' 섹션의 안내를 따르세요. Google Cloud 새 사용자에게는 미화 $300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.
Google Cloud Shell
Google Cloud를 노트북에서 원격으로 실행할 수도 있지만 이 Codelab에서는 Cloud에서 실행되는 명령줄 환경인 Google Cloud Shell을 사용합니다.
Cloud Shell 활성화
- Cloud Console에서 Cloud Shell 활성화를 클릭합니다.
이전에 Cloud Shell을 시작하지 않았으면 설명이 포함된 중간 화면(스크롤해야 볼 수 있는 부분)이 제공됩니다. 이 경우 계속을 클릭합니다(이후 다시 표시되지 않음). 이 일회성 화면은 다음과 같습니다.
Cloud Shell을 프로비저닝하고 연결하는 데 몇 분 정도만 걸립니다.
가상 머신은 필요한 모든 개발 도구와 함께 로드됩니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 이 Codelab에서 대부분의 작업은 브라우저나 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
이 명령어는 초기 Maven 프로젝트 설정과 애플리케이션의 스캐폴딩 코드를 registrations-codelab/
디렉터리에 생성합니다. 다음 섹션에서는 작동하는 애플리케이션을 만드는 데 필요한 코드 편집을 설명합니다.
Cloud Shell 코드 편집기
Cloud Shell 환경에서 코드를 수정하고 확인하는 가장 쉬운 방법은 기본 제공되는 Cloud Shell 코드 편집기를 사용하는 것입니다.
Cloud Shell 인스턴스를 연 후 연필 아이콘을 클릭하여 코드 편집기를 엽니다. 편집기를 사용하면 Initialzr에서 생성된 프로젝트 파일을 직접 수정할 수 있습니다.
6. 데이터베이스 구성
먼저 설정된 Cloud MySQL 데이터베이스에 연결할 수 있도록 애플리케이션을 구성합니다. Spring Cloud GCP 라이브러리는 Cloud MySQL 인스턴스에 연결하는 데 필요한 종속 항목을 제공하는 Cloud MySQL 시작 조건을 제공합니다.
프로젝트 pom.xml에 spring-cloud-gcp-starter-sql-mysql
종속 항목을 추가합니다.
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과 비슷한 것을 확인할 수 있습니다. 이 템플릿은 애플리케이션을 통해 등록된 모든 등록자를 포함하는 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 창에서 미리보기 버튼을 클릭하고 홈페이지가 렌더링되는지 확인합니다. 하지만 웹 컨트롤러가 없기 때문에 UI의 기능은 전혀 작동하지 않습니다. 다음 단계에서 추가됩니다.
애플리케이션을 미리 본 후 CTRL+C
를 눌러 애플리케이션을 종료합니다.
8. Pub/Sub 주제로 등록자 보내기
이 단계에서는 웹 양식을 통해 제출한 등록자가 Cloud Pub/Sub 주제에 게시되는 기능을 구현합니다.
데이터 클래스 추가
먼저 몇 가지 Kotlin 데이터 클래스를 만듭니다. 이들은 Google의 JPA 법인이 될 것이며 양식을 통해 제출된 등록자를 대표하는 Google의 중간 대리인으로도 활동합니다.
데모 패키지에서 새 파일 두 개(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(..)
함수에서 등록자 정보는 Cloud Pub/Sub와 상호작용을 시작하는 데 필요한 상용구 코드를 최소화하는 Spring Cloud GCP Pub/Sub 통합의 편의 클래스인PubSubTemplate
를 사용하여 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 객체 매퍼 빈 추가
컨트롤러에서 Person
객체를 문자열이 아닌 Pub/Sub 주제에 게시합니다. 이는 주제로 전송되는 커스텀 JSON 페이로드에 대한 Spring Cloud GCP 지원을 활용하기 때문에 가능합니다. 라이브러리를 사용하면 객체를 JSON으로 직렬화하고, JSON 페이로드를 주제로 전송하고, 수신 시 페이로드를 역직렬화할 수 있습니다.
이 기능을 활용하려면 ObjectMapper
빈을 애플리케이션 컨텍스트에 추가해야 합니다. 이 ObjectMapper
빈은 애플리케이션에서 메시지를 주고받을 때 JSON과 객체를 직렬화하는 데 사용됩니다. DemoApplication.kt
클래스에서 JacksonPubSubMessageConverter
Spring 빈을 추가합니다.
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
를 추가합니다. 그런 다음 @ServiceActivator
를 사용하여 pubsubInputChannel
에 도착하는 메시지와 함께 호출되도록 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
Preview 버튼을 다시 클릭하고 양식을 작성하고 제출하여 사용자를 등록해 봅니다.
등록된 사용자 링크를 클릭하여 새 등록자가 표에 표시되는지 확인합니다.
축하합니다. 완료되었습니다. 터미널 창에서 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의 자바: https://cloud.google.com/java/
- GCP를 사용하는 샘플 Kotlin 애플리케이션: https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/master/spring-cloud-gcp-kotlin-samples
라이선스
이 작업물은 Creative Commons Attribution 2.0 일반 라이선스에 따라 사용이 허가되었습니다.