본 장표는 인프콘 2022 / 코틀린 멀티플랫폼, 미지와의 조우 세션에 대한 강연 자료입니다.
코틀린은 멀티플랫폼을 지원하는 언어로 Server-side와 Android뿐만이 아니라 JavaScript 엔진이 있는 브라우저나 Node.js도 지원하며, Native 등 다양한 플랫폼에서 쓸 수 있습니다. 이를 이용해 코틀린 코드를 공유하는 단일 코드베이스로 모바일부터 웹과 데스크톱, 서버에 이르기까지 다중 플랫폼 애플리케이션을 작성할 수 있습니다.
본 핸즈온 세션을 통해 코틀린 멀티플랫폼과 함께 리액트, 스프링부트로 웹 애플리케이션의 프론트엔드부터 백엔드까지 직접 개발하며 친해져 보는 시간을 가져보세요. 참가자는 코틀린 멀티플랫폼 프로젝트를 이해하고, 더 나아가 프론트엔드와 백엔드 간의 공유 로직 작성, Kotlin/JS 기반 리액트 및 스프링 웹 프로그래밍을 경험할 수 있습니다.
https://github.com/arawn/building-fullstack-webapp-with-kotlin-multiplatform
https://infcon.day/speaker/박용권-김지헌-코틀린-멀티플랫폼/
1. 코틀린 멀티플랫폼, 미지와의 조우
Building a Full Stack Web App with Kotlin Multiplatform and React, Spring
스프링러너 박용권/김지헌
2. 1
콘텐츠
목차
content
핸즈온 환경 확인하기
핸즈온을 진행하기 위해 필요한 도구와 프로젝트 구성을 확인한다
2 코틀린 멀티플랫폼과 인사하기
코틀린 멀티플랫폼이 무엇인지, 또 어떻게 동작하는지 가볍게 알아본다
3 코틀린 멀티플랫폼과 친해지기
코틀린 멀티플랫폼과 함께 리액트, 스프링으로 웹 애플리케이션을 클라이언트부터 서버까지 함께 개발한다
4 코틀린 멀티플랫폼 좀 더 알아보기
코틀린 멀티플랫폼의 특징과 동작 방식, 그리고 한계를 알아본다
3. 핸즈온 환경 확인하기
5 min exec
https://flic.kr/p/24AGZLS
2022 INFCON
9. 2022 INFCON
IntelliJ IDEA Community Edition 설치하기
https://www.jetbrains.com/idea/download/#section=mac
!
핸즈온에서는 Ultimate 버전이 아닌 Community 버전을 사용합니다.
꼭 최신 버전을 설치해주세요!
10. 2022 INFCON
IntelliJ Plugin Kotest 설치하기
https://plugins.jetbrains.com/plugin/14080-kotest
!
1. IntelliJ를 실행 후 Plugins 창을 선택하세요
2. Marketplace 탭을 선택하세요
3. kotest 를 검색 후 설치하세요
12. 2022 INFCON
핸즈온 프로젝트 구성하기 2/4
https://plugins.jetbrains.com/plugin/14080-kotest
!
1. IntelliJ를 실행 후 Projects 창을 선택하세요
2. Get from VCS 버튼을 선택하세요
13. 2022 INFCON
핸즈온 프로젝트 구성하기 3/4
https://plugins.jetbrains.com/plugin/14080-kotest
!
1. Repository URL 창을 선택해주세요
2. Version control: Git 을 선택해주세요
3. URL 에 프로젝트 저장소 주소를 입력하세요
4. Directory: 를 확인해주세요
5. Clone 버튼을 선택하세요
https://github.com/arawn/building-fullstack-webapp-with-kotlin-multiplatform.git
15. 2022 INFCON
핸즈온 프로젝트 실행하기 1/4
src/jvmMain/kotlin/todoapp/TodoServerApplication.kt 파일을 열어주세요
fun main args: Array String 함수를 실행하세요
실행 아이콘을 클릭해서
애플리케이션을 실행 할 수 있어요
16. 2022 INFCON
핸즈온 프로젝트 실행하기 2/4
애플리케이션이 정상적으로 실행되면 Run 콘솔에서 로그를 볼 수 있어요
Started TodoServerApplicationKt in x.xxx seconds JVM running for x.xxx 로그를 확인하세요
17. 2022 INFCON
핸즈온 프로젝트 실행하기 3/4
src/jvmTest/kotlin/todoapp/data/r2dbc/R2dbcTodoRepositorySpecs 를 열어주세요
테스트 코드를 실행하고 성공 여부를 확인하세요
테스트 실행 아이콘을 클릭해서
테스트를 실행 할 수 있어요
18. 2022 INFCON
핸즈온 프로젝트 실행하기 4/4
브라우저를 열고 localhost:8080/main.js 자원을 확인해요
만약 Whitelabel Error Page가 보이면 다음 안내사항을 따라하세요
19. 2022 INFCON
핸즈온 프로젝트 실행하기 4.1/4
src/jsMain/kotlin/todoapp/TodoClientApplication.kt 파일을 열어주세요
13번 라인 TODO ... 내부 문자열을 변경 후 애플리케이션을 재시작 후 다시 main.js 에 접근해보세요
문자열 내부 내용을 변경해보세요!
전 마지막에 느낌표를 덧붙였어요!
24. 2022 INFCON
사용 사례
안드로이드 iOS 앱 동시 지원 Android and iOS applications
풀스택 웹 애플리케이션 Full stack web applications
멀티플랫폼 라이브러리 Multiplatform libraries
모바일부터 웹과 데스크톱, 서버까지 비즈니스 논리를 사용 Common code for mobile and web applications
25. 2022 INFCON
코틀린 멀티플랫폼의 작동 방식
Native
Code
JS
Code
JVM
Code
Common Kotlin
Kotlin/JVM Kotlin for server side
Kotlin/JS Kotlin for JavaScript
Kotlin/Native
Common
Kotlin
Kotlin
/JVM
Kotlin
/JS
Kotlin
/Native
26. 2022 INFCON
모든 플랫폼에 걸쳐 공유되는 코드, 단 UI는 제외하고...!
코틀린은 모든 플랫폼에 걸쳐 동작 가능한 코드를 작성 할 수 있는 매커니즘을 제공한다
모든 플랫폼에 적용되는 공통된 비즈니스 로직을 작성해서 공유 할 수 있다
하지만 모든 플랫폼에서 단독으로 실행 가능한 애플리케이션을 지향하지 않는다
commonMain
iosMain jvmMain jsMain desktopMain
iosArm64Main iosX64Main linuxX64Main mingwX64Main macosX64Main
27. 2022 INFCON
멀티플랫폼 프로젝트 구조 이해하기 : 그레이들 플러그인 구성
plugins {
kotlin("multiplatform") version "1.6.21"
}
kotlin {
jvm {
withJava()
}
js(IR) {
binaries.executable()
browser { ... }
}
sourceSets {
val commonMain by getting {
dependencies { ... }
}
val commonTest by getting
val jsMain by getting
val jsTest by getting
val jvmMain by getting
val jvmTest by getting
}
}
https://kotlinlang.org/docs/multiplatform-dsl-reference.html
!
33. 2022 INFCON
플랫폼별 API 연결하기
https://kotlinlang.org/docs/multiplatform-connect-to-apis.html
!
// file:src/commonMain/kotlin/todoapp/domain/TodoId.kt
expect interface TodoIdGenerator {
open fun generateId(): TodoId
}
class UUIDTodoIdGenerator: TodoIdGenerator
// file:src/jsMain/kotlin/todoapp/domain/TodoIdGenerator.kt
actual interface TodoIdGenerator {
actual fun generateId() = TodoId(external.uuid.v4().lowercase())
}
// file:src/jvmMain/kotlin/todoapp/domain/TodoIdGenerator.kt
actual interface TodoIdGenerator {
actual fun generateId() = TodoId(UUID.randomUUID().toString().lowercase())
}
Common Kotlin & Kotlin/JS, Kotlin/JVM
34. 2022 INFCON
코틀린 코루틴을 사용한 동시성 프로그래밍
Common Kotlin
import kotlinx.coroutines.*
fun useTodos(
find: TodoFind,
registry: TodoRegistry,
coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default
): UseTodosProps {
...
return UseTodosProps(
todos = todos,
register = { text ->
CoroutineScope(coroutineDispatcher).async {
withContext(coroutineContext) {
registry.register(text = text)
}
fetch()
}
},
)
}
35. 2022 INFCON
멀티플랫폼에서 동작하는 코틀린 코루틴 라이브러리
경량 스레드 기반으로 현대적인 동시성 프로그래밍을 지원하는 공식 라이브러리다
멀티플랫폼을 지원하며, 일관된 방식으로 동시성 프로그래밍을 작성 할 수 있다
https://kotlinlang.org/docs/coroutines-overview.html
!
36. 2022 INFCON
코틀린 코루틴을 광범위하게 지원하는 스프링 프로젝트
fun handler : Mono Void becomes suspend fun handler
fun handler : Mono T becomes suspend fun handler : T or suspend fun handler : T?
fun handler : Flux T becomes fun handler : Flow T
https://docs.spring.io/spring-framework/docs/current/reference/html/languages.html#coroutines
!
37. 2022 INFCON
코틀린 직렬화를 사용한 JSON 직렬화와 역직렬화
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
@kotlinx.serialization.Serializable
data class Todo(...)
val todo = Todo(
id = TodoId("test"),
text = "Task One",
completed = false,
createdDate = LocalDateTime.parse("2020-08-26T13:00:00")
)
val encoded = Json.encodeToString(todo)
assertEquals("""
{"id":{"id":"test"},"text":"Task One","completed":false,"createdDate":"2020-08-26T13:00"}
""".trimIndent(), encoded)
val decoded = Json.decodeFromString<Todo>(encoded)
assertEquals(todo, decoded)
Common Kotlin
38. 2022 INFCON
멀티플랫폼에서 동작하는 코틀린 직렬화 라이브러리
객체를 문자열, 바이트 배열 또는 다양한 형식으로 직렬화하고, 다시 역직렬화 할 수 있는 공식 라이브러리다
JSON, CBOR, ProtoBuf, Properties 형식을 지원하고, 이외 사용자 정의 형식도 사용 할 수 있다
멀티플랫폼을 지원하기 때문에 모든 플랫폼에서 일관된 방식으로 동작한다
https://kotlinlang.org/docs/serialization.html
!
39. 2022 INFCON
Ktor 클라이언트로 Web API 호출
Common Kotlin
class HttpClientTodoManager(
private val httpClient: HttpClient,
private val webAPIProperties: WebAPIProperties = WebAPIProperties()
): TodoFind, TodoRegistry, TodoModification, TodoCleanup {
override suspend fun all(): Todos {
return httpClient.get(webAPIProperties.findAllUrl).body()
}
override suspend fun register(text: String): TodoId {
TodoValidator.validate(text)
return httpClient.post(webAPIProperties.registerUrl) {
contentType(ContentType.Application.Json)
setBody(WriteTodoCommand(text))
}.body<TodoId>().apply {
logger.info { "Registered todo (id: $this)" }
}
}
}
40. 2022 INFCON
비동기 서버 및 클라이언트 구축을 돕는 Ktor 프레임워크
HTTP 및 웹 소켓을 위한 통합 인터페이스를 제공한다
멀티플랫폼을 지원하며, 코틀린 코루틴과 자연스럽게 통합됀다
https://ktor.io/docs/getting-started-ktor-client.html
!
41. 2022 INFCON
kotlin logging 라이브러리를 사용한 로깅
Common Kotlin
import mu.KotlinLogging
class InMemoryTodoManager(
private val todoIdGenerator: TodoIdGenerator = DefaultTodoIdGenerator()
): TodoFind, TodoRegistry, TodoModification, TodoCleanup {
private val logger = KotlinLogging.logger("todoapp.application.support.InMemoryTodoManager")
override suspend fun register(text: String): TodoId {
return Todo.create(text = text, idGenerator = todoIdGenerator).apply {
todos.add(this)
logger.info { "Registered todo (id: $id)" }
}.id
}
...
private fun loadTodoById(id: TodoId) =
todos.firstOrNull { it.id == id } ?: error("Not Found Todo (id: $id)")
}
42. 2022 INFCON
코틀린을 위한 경량 로깅 프레임워크인 kotlin logging
코틀린의 관용적 로깅 방법 및 로거 모범 사례를 기반으로 간결한 방식으로 사용 할 수 있다
람다를 통해 지연 평가되는 문자열을 로그로 기록하며, 멀티플랫폼을 지원한다
https://ktor.io/docs/getting-started-ktor-client.html
!
45. 2022 INFCON
외부 자바스크립트 라이브러리 사용
https://kotlinlang.org/docs/browser-api-dom.html
!
// file:src/jsMain/kotlin/external/issorted/is-sorted.kt
@JsModule("is-sorted")
@JsNonModule
external fun <T> sorted(a: Array<T>): Boolean
// file:src/jsTest/kotlin/external/IsSortedTest.kt
import kotlin.test.Test
class IsSortedTest {
@Test
fun thingsShouldWork() {
assertTrue(sorted(arrayOf(1,2,3)))
}
@Test
fun thingsShouldBreak() {
assertFalse(sorted(arrayOf(3,1,2)))
}
}
Kotlin/JS
46. 2022 INFCON
Kotlin/JS로 작성한 리액트 앱
// file:src/jsMain/kotlin/todoapp/TodoClientApplication.kt
import react.dom.client.createRoot
fun main() {
val container = document.getElementById("root") ?: error("Couldn't find root container!")
createRoot(container = container).render(
WelcomePage.create {
name = "SpringRunner"
}
)
}
// file:src/jsMain/kotlin/todoapp/ui/welcome/WelcomePage.kt
import react.*
external interface WelcomePageProps : Props {
var name: String
}
val WelcomePage = FC<WelcomePageProps> { props ->
val (name, setName) = useState(props.name)
section { ... }
}
Kotlin/JS
47. 2022 INFCON
DSL로 작성한 스프링의 경량 함수형 프로그래밍 모델 with WebFlux.fn and WebMvc.fn
https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-fn
!
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.router
class IndexRouter : RouterFunction<ServerResponse> {
private val delegate = router {
accept(MediaType.TEXT_HTML).nest {
GET("/") {
ok().contentType(MediaType.TEXT_HTML).bodyValue(indexHtml)
}
}
}
private val indexHtml = buildString { ... }
override fun route(request: ServerRequest) = delegate.route(request)
}
Kotlin/JVM
48. 2022 INFCON
영역 특화 언어 DSL; Domain Specific Language
코드의 가독성과 유지 보수성을 좋게 유지 할 수 있다
코드 자동완성 기능을 누릴 수 있다
컴파일 시점에 문법 오류를 알 수 있다
https://en.wikipedia.org/wiki/Domain-specific_language
!
49. 2022 INFCON
타입 안전성을 보장하는 코틀린 HTML DSL with Kotlin Multiplatform
https://github.com/Kotlin/kotlinx.html
!
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
private val indexHtml = buildString {
appendLine("<!DOCTYPE html>")
appendHTML(xhtmlCompatible = true).html {
head {
title("KMP • TodoMVC")
meta(charset = "utf-8")
link(href = "/webjars/todomvc-common/1.0.5/base.css", rel = "stylesheet")
link(href = "/webjars/todomvc-app-css/2.4.1/index.css", rel = "stylesheet")
}
body {
div {
id = "root"
}
script(src = "/main.js") { }
}
}
}
Kotlin/JVM
50. 코틀린 멀티플랫폼 좀 더 알아보기
10 min exec
https://flic.kr/p/QXVXYA
2022 INFCON
52. 2022 INFCON
코틀린 컴파일러
frontend
JVM backend JS backend Native backend
*.class *.js *.so
source code : *.kt
syntax tree +
semantic info
코틀린 1.7부터 새로운 컴파일러인 K2 가 알파 단계로 출시되었어요
!
컴파일러 내부
53. 2022 INFCON
플랫폼 추상화 매커니즘
JavaScript
expect fun / class
actual fun / class
declaration
actual fun / class
declaration
actual fun / class
declaration
JVM / Android Native
Common
import JVM libraries
import JS libraries
(with NPM)
import Native libraries
54. 2022 INFCON
다시, 코틀린 멀티플랫폼의 작동 방식
main test
main test
jvmMain jvmTest jsMain jsTest
commonTest
commonMain
대상 플랫폼
컴파일러 컴파일러
대상 플랫폼
소스 코드
}
모든 플랫폼을 대상으로
테스트 되는 코드
모든 플랫폼을 대상으로
컴파일되는 공유 코드
JVM 플랫폼으로
컴파일되는 서버 코드
의존관계
jvm js
JS 로 컴파일되는
클라이언트 코드
55. 2022 INFCON
모든 플랫폼을 대상으로 비즈니스 논리를 공유
https://kotlinlang.org/docs/multiplatform-share-on-platforms.html
!
56. 2022 INFCON
코틀린 멀티플랫폼의 한계
목적과 필요에 따른 기술 선택과 조직 구성에 대한 고민이 필요합니다
아직 덜여문 생태계로 스스로 문제를 해결해야 하는 상황을 자주 만날 수 있다는 사실을 이해해야 합니다
제약적인 개발 환경과 프레임워크 또는 라이브러리 에 대한 이해가 필요합니다
58. 2022 INFCON
참고자료
Kotlin Multiplatform
Building a Full Stack Web App with Kotlin Multiplatform
Building Web Applications with React and Kotlin/JS
Get Started with Kotlin Multiplatform and Spring Boot
Diving into Kotlin/ JS with Sebastian Aigner
Book: Mastering Kotlin
Book: Simplifying Application Development with Kotlin Multiplatform Mobile
Book: Kotlin Multiplatform by Tutorials