자바개발자가 보기에 Kotlin 에서 좋아보이는 것들 - 5
비동기 프로그래밍 기술 (Asynchronous Prgramming Techniques)
블로킹은 사용자들을 기다리게 만들고, 애플리케이션을 확장하는데 병목 현상을 초래한다.
애플리케이션에서 블로킹을 어떻게 막을 것인지 여러 방법이 있다.
쓰레딩 Threading
블로킹을 막기 위한 가장 잘 알려진 방법. 한 메인 프로그램에서 여러 쓰레드가 실행되며 동시 처리한다.
fun postItem(item: Item) {
val token = preparePost()
val post = submitPost(token, item)
processPost(post)
}
fun preparePost(): Token {
// 요청을 보내 토큰을 받음
return token
}
하지만 단점이 있다.
- 쓰레드는 비싸다. 컨텍스트 스위칭 비용이 크다.
- 쓰레드는 쓰레드풀 안에서 갯수가 제한적이다. 이 때문에 서버 애플리케이션에서 병목현상을 일으킨다.
- 쓰레드를 지원하지 않는 플랫폼도 있다. (예. JavaScript)
- 쓰레드는 어렵다. 멀티쓰레드 프로그래밍에서 race condition 을 막고, 쓰레드를 디버깅하는게 어렵다.
콜백 Callbacks
콜백 함수를 실행 시키고자하는 다른 함수의 파라미터로 넘기고, 처리가 완료되면 넘겨준 콜백 함수를 실행시키는 방법이다.
fun postItem(item: Item) {
preparePostAsync { token ->
submitPostAsync(token, item) { post ->
processPost(post)
}
}
}
fun preparePostAsync(callback: (Token) -> Unit) {
// 요청을 보내 토큰을 받고 바로 return
// 나중에 콜백 처리
}
우아한 해결책처럼 보이지만, 콜백도 몇몇 이슈가 있다.
- 계속해서 안에 담겨지게 될 수 있는(nested) 콜백 처리가 어렵다. 가독성이 안좋다. 이를 콜백 헬 또는
titled chrismas tree
라 부른다. (모양이 트리처럼 생김) - 에러 처리가 복잡하다.
콜백은 JavaScript 같은 이벤트루프 아키텍처에서 주로 사용되지만, 그런 구조에서조차 promise 나 reactive extensions 로 옮겨가고 있다.
퓨처, 프로미스 등 (Futures, Promises, and others)
퓨처와 프로미스는 같은 용어이다. (언어, 플랫폼에 따라 다르게 부른다.)
우리가 어떤 함수를 호출하면 언젠가 Promise
라는 객체를 받기를 약속받고, 우리는 그 객체를 사용할 수 있다.
fun postItem(item: Item) {
preparePostAsync()
.thenCompose { token ->
submitPostAsync(token, item)
}
.thenAccept { post ->
processPost(post)
}
}
fun preparePostAsync(): Promise<Token> {
// 요청을 보내 토큰을 받고 Promise 로 감싸서 리턴
return promise
}
이런 방식은 우리가 프로그래밍하는 방식을 좀 다르게 한다.
- callback 을 사용하던 환경은 탑다운 형식의 절차적 접근이었다면, promise 는 체이닝 콜을 가진 구성적 모델(compositional model) 이다.
thenCompose
,thenAccept
등 새로운 API 를 배워야 한다. 이는 또 플랫폼마다 다르다.Promise
같은 새로운 반환 타입을 받고, 이를 우리가 원하는 방식으로 적용해야 한다.- 에러 처리가 복잡하다.
리액티브 확장 (Reactive Extensions)
리액티브 확장(Rx, Reactive Extensions)는 C# 에서 도입됐는데, .NET
플랫폼에서 명확히 사용된 이후로, 넷플릭스가 java 에 도입하여 RxJava 를 만들고, 이후 RxJS (JavsScript) 등 다른 언어에도 구현체가 만들어졌다.
Rx 의 아이디어는 데이터를 스트림 (무한한 양의 데이터) 이라 생각하고, 관측가능하다고 생각하여 옵저버 패턴으로 이 데이터를 관리하는 것이다.
Rx 는 정해진 인터페이스하에 다른 언어에서도 일관성있는 API 를 제공하는 것이 큰 장점이다. 그리고 에러 처리에도 훌륭하게 수행한다.
코루틴 (Coroutines)
코틀린의 비동기 코드에 대한 접근 방식은 코루틴이다. 아이디어는 함수를 잠시 중단(suspend)해 놓았다가 나중에 다시 재개(resume)하는 것이다.
fun postItem(item: Item) {
launch {
val token = preparePost()
val post = submitPost(token, item)
processPost(post)
}
}
suspend fun preparePost(): Token {
// 요청을 보내 토큰을 받고, 코루틴을 suspend
return suspendCoroutine { ... }
}
코루틴의 큰 장점은 블로킹 코드를 작성하는 것처럼 논블로킹 코드를 작성할 수 있다는 것이다.
- 함수 앞에
suspend
키워드가 붙는 것을 제외하면 거의 비슷하다.launch
블록 등 다른 처리도 있음. - 새로운 API 를 배울 필요 없이, 프로그래밍 모델과 API 는 기존과 같다.
- 플랫폼과는 독립적이다. JVM 이든, JavaScript 또는 다른 플랫폼이든 코드는 같다.
- 코루틴 개념은 새로운 개념은 아니고 이전에
Go
에서도goroutine
으로 사용되었다. 코루틴의 장점은suspend
를 제외하면 대부분 기능은 라이브러리에 위임돼있다. 따라서 적용하기 쉽다.
구조분해 선언 (Destructing Declarations)
val (id, title) = book
이 문버비 가능한 이유는 위 코드가 아래 코드로 컴파일시 변환되기 때문이다.
val id = book.component1()
val title = book.component2()
componentN()
메소드는 코틀린 코드 컨벤션이고, 다른 메소드명으로 만들어도 된다. 해당 타입의 프로퍼티를 가져올 수 있으면 되고, 이 떄 operator
키워드가 붙여있으면서 정의되어야 한다.
data class
를 사용하면 componentN()
을 자동으로 만들어주는데, 덕분에 구조분해 선언을 자연스럽게 바로 사용할 수 있다.
data class Book(val id: Long, val title: String)
map 도 아래와 같이 쉽게 key, value 를 꺼내올 수 있다.
for ((key, value) in map) {
//
}
표준 라이브러리에 iterator()
과 componentN()
이 이미 잘 구현되어있기 때문이다.
operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
operator fun <K, V> Map.Entry<K, V>.component2() = getValue()
가져오지 않을 값은 언더스코어(_) 로 생략할 수 있다.
val (_, title) = book
- _ 는
componentN()
호출이 스킵된다.
'Java, Kotlin, Spring > kotlin' 카테고리의 다른 글
자바개발자가 보기에 Kotlin 에서 좋아보이는 것들 - 4 (0) | 2022.11.07 |
---|---|
자바개발자가 보기에 Kotlin 에서 좋아보이는 것들 - 3 (0) | 2022.11.03 |
자바개발자가 보기에 Kotlin 에서 좋아보이는 것들 - 2 (1) | 2022.10.15 |
자바개발자가 보기에 Kotlin 에서 좋아보이는 것들 - 1 (1) | 2022.10.13 |
Kotlin 서버 개발하기 2 (1) | 2022.09.30 |
댓글