본문 바로가기
Android/Network

[2023 GDG Android Korea] Retrofit2 대신 Ktor는 어떠신가요?

by 굿햄 2023. 4. 12.

2023.04.01(토)에 구글 스타트업 캠퍼스에서 열린 , Super init (version = 4) 행사

토요일 아침 안드로이드 개발자 컨퍼런스 Super init 4기에 참여하기 위해 서울로 향했습니다.

이 날 행사장 내부가 거의 찰 정도로 인원이 많이 모인 것을 볼 수 있었습니다.
연령대도 20부터 30대까지 다양한 모습을 보니 색달랐습니다 ㅎㅎ

 

행사는 총 6개의 부로 나뉘어 있었습니다

  • 안드로이드 주니어, 시작점에 서기까지 / 임준섭님
  • API 통신, Retrofit 대신 Ktor 어떠신가요? / 유광무님
  • 선언형 UI가 대세임을 "선언"합니다. (Compose 학습부터 적용까지의 일대기) / 이현우님
  • 지라 자동화 어디까지 가능할까요? / 이하나님
  • 개발자의 글쓰기 - 우당탕탕 Design Document 작성기 / 정세희님
  • 인생게임 - ver. 모바일개발자 / 손예진님

이 중 눈에 가장 띄었던 Ktor에 대해 글을 작성하고자 합니다.


API 통신, Retrofit 대신 Ktor 어떠신가요?

Ktor의 Multiplaform 지원에 대해

 

안드로이드 개발자라면 네트워크 라이브러리를 필수불가결하게 사용할 것 입니다.

네트워크 라이브러리는 Rest API를 호출하여 가져온 Response를 변환해 코드에서 활용할 수 있도록 도와주는 라이브러리 입니다.

 

저 같은 경우 Java를 사용할 당시에는 httpurlconnection를 통해 직접 구현해보고,

그 다음 OkHttp 라이브러리 기반의 Retrofit2가 대중적으로 알려져 사용하고 있습니다.

 

이 외에도 Volley, Ktor 같은 라이브러리도 존재하는데, 이 Ktor가 Retrofit 과 달리 jvm에 의존성이 없으며,
Kotlin/Native, Kotlin/JVM 환경에서 컴파일이 되기에 KMM(Kotlin Multiplatform Mobile) 환경에서 사용하기 적합하다는 것이였습니다.

 

듣다보니 새로운 내용도 있었고, 저희들의 관심을 끌기엔 충분히 매력있기에 관련 자료를 좀 더 조사해 보았습니다.

 

🔍 Retrofit2는 Kotlin/Native 컴파일러를 지원하지 않는다.

Android 한정으로 Kotlin을 사용하여 개발하다보니 저는 그 동안 jvm은 Kotlin 언어로 만들 수 있는 컴파일 대상 중 하나라는 것을 간과하고 있었습니다.

 

Kotlin의 컴파일러는 세 가지 플랫폼을 지원하고 있습니다.

Kotlin/JVM JVM(Java Virtual Machine)을 타켓으로 컴파일, Android 앱 빌드 시 해당 플랫폼을 타겟으로 합니다.
Kotlin/JS ES5을 대상으로 한 Javascript로 변환하는 기능을 제공합니다.
Kotlin/Native Virtual Machine 없이 구동할 수 있도록 네이티브 바이너리로 컴파일합니다. Kotlin 컴파일러용 LLVM(Low Level Virtual Machine) 기반의 백엔드와 Kotlin standard 라이브러리를 가진 native 구현이 포함됩니다.
LLVM: 재사용 가능한 컴파일러이자, 툴체인 기술의 집합체. 바이너리로 내 코드를 컴파일하는데 사용되는 라이브러리
프론트엔드에서 Kotlin, Python, C++과 같은 고급 언어를 읽고 파싱하여 IR(Intermediate Representation)로 변환합니다.
미들엔드에서 변환된 IR을 Optimizer(최적화)하여,
백엔드에서 이 IR을 가지고 장치의 아키텍처에 맞는 기계어 바이너리 코드로 변환합니다.

 

문제는 타켓에 따라 지원되는 라이브러리의 차이가 있다는 것 입니다.

https://kotlinlang.org/api/latest/jvm/stdlib/

Retrofit2는 jvm을 타겟으로 하는 라이브러리에 의존하고 있어 Kotlin/Native와 Kotlin/JVM를 함께 타겟으로 하는 KMM상에서의 공통 로직으로 구현할 수 없습니다.

 

Ktor는 이러한 한계를 벗어나 공통로직으로 구현할 수 있습니다.

 

🔍 Setup 과정의 간소화

Retrofit2에서 서버와 Rest API 통신을 진행하기 위해선 제공하는 함수를 통해 Builder를 준비하고, 받아온 Request 결과 값(Json, Protobuf)을 Service Interface(e.g. data class)의 구현체(Implementation)으로 변환하여 데이터로 사용할 수 있도록합니다.

 

Ktor(client) 역시 비슷한 과정을 거치나, 몇 가지의 차이점이 있습니다.

 

Json 형식의 Request 값을 데이터 객체로 변환하기 위해, Retrofit은 'ConverterFactory'를 Ktor는 install 함수(플러그인 설치 기능)에 ContentNegotiation를 전달하여 Converter를 추가합니다.

 

아래의 예시는 선호하는 아키텍처에 따라 다르지만, 예시를 위해 간단하게 표현하였습니다.

 

 Retrofit 예시

// Retrofit
data class Article(
    val id: String,
    val title: String,
    val content: String
)

private const val BASE_URL = "https://daryeou.dev/"

interface RetrofitService {
    @GET("articles")
    suspend fun getArticles(): List<Article>
}

val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    
val service = retrofit.create(RetrofitService::class.java)

// API 호출
// suspend 함수이므로 coroutineScope내에서 호출해야하나, 예제에서는 runBlocking으로 대체합니다.
runBlocking {
    val result = service.getArticles()
}

 

▪ Ktor 예시

// Ktor
@Serializable
data class Article(
    val id: String,
    val title: String,
    val content: String
)

private const val BASE_URL = "https://daryeou.dev/"

val ktorClient = HttpClient(CIO){
    defaultRequest {
        url {
            protocol = URLProtocol.HTTPS
            host = BASE_URL
        }
    }
    install(ContentNegotiation) {
        json() // for json
    }
}

object Service {
    suspend fun getArticles(): List<Article> {
        ktorClient
        .get<HttpResponse>(path = "articles")
        .body<List<Article>>()
    }
}

// API 호출
// suspend 함수이므로 coroutineScope내에서 호출합니다.
val result = Service.getArticles()

Client를 만드는 과정에서 유사하지만 다른 방식으로 생성하는 것을 볼 수 있습니다.

 

간단하다는 것에 다소 주관적인 요소가 섞여있을 수 있으나,

Retrofit에서는 함수 형식으로 선언하여 팩토리 패턴으로 클라이언트를 생성하던 것을, Ktor에서는 Scope를 할당하여 DSL 형식으로 작성함으로써 가독성을 높인 것으로 보입니다.

 

 

🔍 Ktor는 Serialization을 지원한다.

Retrofit2 역시 Kotlin-Serialization을 지원합니다.

 

Retrofit2 의 예시로 자주 쓰이는 GsonConverter는 Kotlin의 Null-safety와 Default value를 지원되지 않는다는 큰 문제점을 갖고 있습니다.

 

그러나 이 문제는 Kotlin-Serialization를 사용하여 해결할 수 있으며, Ktor 역시 해당 컨버터를 지원합니다.

// Retrofit
...
val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(
    Json {
            isLenient = true // 따옴표 규칙 완화
    	    ignoreUnknownKeys = true // Field 값이 없는 경우 무시할지
    	    coerceInputValues = true // null 또는 잘못된 값이 들어간경우 default property value로 대체
	}
    .asConverterFactory("application/json".toMediaType()))
    .build()
...

 

// Ktor
...
val ktorClient = HttpClient(CIO){
    defaultRequest {
        url {
            protocol = URLProtocol.HTTPS
            host = BASE_URL
        }
    }
    install(ContentNegotiation) {
        json(
            Json {
                isLenient = true
    	        ignoreUnknownKeys = true
    	        coerceInputValues = true
            }
        )
    }
}
...

 

이 외에도 Mock 테스트를 지원하는 등 Retrofit2와 비교해도 손색이 없을 정도라는 생각이 들었습니다.

이 중 강점은 역시 Native-Kotlin을 지원한다는 점인 듯 하네요.

 


ⓒ 굿햄 2022. daryeou@gmail.com all rights reserved.

댓글