Android 개발/Kotlin

Retrofit 10년 프로젝트에 Ktor를 얹을까 — 6년차가 따져본 갈아탈 자리

stackD 2026. 8. 2. 18:00
반응형

 

얼마 전에 KMP를 붙여보겠다고 iOS 모듈에 레트로핏(Retrofit) 인터페이스를 그대로 가져갔다가, 빌드가 통째로 깨지더라고요. 원인은 Proxy 의존성이었습니다.

 

레트로핏은 어노테이션 인터페이스를 java.lang.reflect.Proxy로 런타임에 구현체를 만들어내는데요. 이 리플렉션이 JVM에만 있다 보니, iOS 타깃에서는 그 자리에서 멈춰버립니다. "10년을 멀쩡히 썼는데 왜 갑자기"가 아니라, 레트로핏은 처음부터 안드로이드·JVM 위에서 살도록 만들어진 라이브러리라서 그래요.

 

그래서 케이토르(Ktor)로 갈아탈지 한참 따져봤습니다. 한참 들여다보고 나니, 갈아탈 진짜 기준은 성능이 아니더군요.

 

안드로이드 네트워킹 라이브러리 교체, 방아쇠는 성능이 아니었습니다

마이그레이션을 고민하게 된 계기가 속도였다면 저는 시작도 안 했을 겁니다. 일반적인 앱 환경에서 두 라이브러리의 응답 속도 체감 차이는 거의 없어요.

 

검토의 방아쇠를 당긴 건 KMP, 즉 코틀린 멀티플랫폼이었습니다. iOS와 네트워크 로직을 공유하려는 순간 레트로핏의 JVM 의존성이 딱 발목을 잡았어요.

 

마침 두 라이브러리가 나란히 세대교체를 했다는 점도 컸습니다. 케이토르 3.0이 kotlinx.io 기반으로 IO를 손봤고, 레트로핏은 이미 2.6.0부터 suspend를 지원했고 3.0에서는 OkHttp 4.12 기반으로 갈아끼우고 예외 전파(suspend 함수가 Throwable 전반을 안전하게 던지도록)를 다듬었습니다. Ktor 3.0·Retrofit 3.0이 나란히 메이저를 올린 지금이 비교하기 딱 좋은 시점이었어요.

 

 

안드로이드 단독 프로젝트라면 Retrofit 유지가 답입니다

iOS 공유 계획이 없는 순수 안드로이드 프로젝트라면 이야기가 꽤 싱겁게 끝납니다.

 

레트로핏 3.0은 코루틴 친화성을 한층 더 다듬었어요. 예전처럼 Call<T>로 감쌀 필요 없이 이렇게 바로 받아오면 됩니다.

 

interface UserApi {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: String): User
}

 

게다가 레트로핏 2.x와 바이너리 호환이라 버전만 올려도 부담이 적은 편입니다. 어노테이션 선언형 방식은 인증·에러 처리 같은 반복 로직에서 코드량이 적게 나오기도 하고요.

 

10년간 쌓인 OkHttp 인터셉터, 사내 유틸리티, 스택 오버플로에 깔린 디버깅 자산까지 생각하면, 안드로이드 단독 프로젝트에서 굳이 케이토르로 넘어갈 명분은 약합니다.

 

Ktorfit과 KMP가 만드는 진짜 차이

반대로 케이토르가 유일한 답이 되는 자리도 분명합니다.

 

케이토르 클라이언트는 OkHttp, Darwin(iOS), CIO, JS, WASM 같은 엔진을 갈아 끼우는 구조라, 멀티플랫폼을 처음부터 제대로 받쳐줍니다. 레트로핏 멀티플랫폼은 공식 로드맵에 없으니, JVM 바깥 플랫폼 지원이 필수라면 현재로선 케이토르 외엔 마땅한 대안이 없는 상황이에요.

 

근데 케이토르의 DSL 문법이 낯선 분들한테 다리를 놓아주는 게 케이토르핏(Ktorfit)입니다. 케이토르 위에 레트로핏 스타일 어노테이션을 그대로 얹어주는 서드파티 라이브러리거든요. 기존 인터페이스 문법을 거의 재활용하면서 KMP 생태계로 들어갈 수 있어서, 익숙해지는 시간을 확 줄여줍니다.

 

Retrofit·Ktor·Ktorfit 구현 체감 비교

셋 다 같은 API로 짜보니까 갈리는 지점이 선명했어요.

 

단순 GET 하나는 케이토르 DSL이 제일 깔끔합니다.

 

val user: User = client.get("users/$id").body()

 

근데 토큰 갱신, 공통 헤더, 에러 분기 같은 게 얽히기 시작하면 이야기가 달라져요. 이럴 땐 레트로핏·케이토르핏의 어노테이션 방식이 중복 코드를 더 잘 줄여줬습니다. DSL은 자유로운 대신, 공통 로직이 복잡해질수록 같은 코드를 손으로 반복하게 되는 함정이 있었어요.

 

한 가지 더. 케이토르 엔진으로 OkHttp를 그대로 꽂으면 기존 인터셉터·로거·캐시 인프라를 살릴 수 있습니다. 신규 모듈만 케이토르로 짜고 레거시는 그대로 두는 점진적 이전이 가능한 구조예요. 한 번에 다 갈아엎지 않아도 된다는 게 제일 마음에 들었습니다.

 

 

갈아탈 자리 vs 그냥 둘 자리, 판단 기준

따져본 걸 체크리스트로 정리하면 이렇습니다.

 

그냥 둘 자리 (Retrofit 유지)

 

  1. KMP·iOS 코드 공유 계획이 없는 순수 안드로이드 프로젝트일 때.
  2. 이미 레트로핏 3.0으로 suspend 함수를 잘 쓰고 있을 때.
  3. 팀 전체가 레트로핏·OkHttp 생태계에 익숙하고 사내 자산이 많을 때.
  4. 10년 운영된 레거시 네트워킹 레이어를 건드리는 리스크가 부담스러울 때.

 

갈아탈 자리 (Ktor 전환)

 

  1. iOS와 네트워크 로직 공유가 프로젝트의 핵심 요구사항일 때.
  2. WASM, SSE 같은 JVM 바깥 플랫폼·신기술 지원이 필요할 때.
  3. OkHttp 말고 다른 엔진으로 교체할 일이 있을 때.

 

레트로핏 문법이 익숙한데 KMP는 가야 한다면, 신규 모듈부터 케이토르핏을 얹고 기존 코드는 그대로 두는 절충안이 투입 대비 효과가 가장 안정적이라고 봅니다.

 

진짜 비용은 코드가 아니라 팀입니다. 10년 치 디버깅 노하우와 사내 자산을 포기하는 조직적 비용까지 저울에 올려야, "최신 기술이니까"라는 막연한 이유로 멀쩡한 레이어를 흔드는 일을 피할 수 있어요.

 

저라면 안드로이드 단독 프로젝트의 네트워킹 스택은 당분간 레트로핏에 그대로 둡니다. 코드 공유 로드맵에 iOS가 올라오는 그날, 그때 케이토르핏 모듈을 새로 파기 시작해도 늦지 않더라고요.

 

 

반응형
LIST