Android, Ktor + OpenApi Generator 도전?적용?기

Kim, Sujin·2023년 10월 15일
2

OpenApi Generator

목록 보기
2/2
post-thumbnail

지난 시간에 이어, OpenApi Generator 적용기 2탄입니다.
(1탄: https://velog.io/@_im_ssu/OpenApi-Generator-in-Android)

1탄에서는, OpenApi Generator 그게 뭔데, 어떻게 하는 건데! 를 알아보기 위한 테스트를 다루었는데요.
이번 2탄에서는, 실제 적용을 어떻게 할지, 적용이 가능할지를 알기 위한 세팅 및 테스트 작업을 다뤄보도록 할게요.

서론: Ktor, 이유

저희가 개발할 서비스는 우테코 크루들을 위한 공지&출결 앱 서비스 인데요.
다만,
(1) 우테코 내에는 아직 ios 과정은 없어, 아이폰을 쓰는 크루들을 위한 앱 서비스는 부재하게 되어요. (물론 웹 서비스 사용이 가능할 예정이어요!)
(2) 또한, kotlin multiplatform을 경험&시도 해보고자 하는 욕구가 있어요.
(3) 현업 개발자가 아닌 학습을 하고 있는 크루로서, 짱짱 레아와 함께 개발할 수 있는 지금, 이것저것 해보기에 대한 두려움을 갖지 않기로 했습니다. (a.k.a. 낭만주도개발)
물론 러닝 커브에 대한 걱정이 많았지만, 앞으로 개발을 하며 경험해보기 힘들 수 있는 것들을 이번 기회에 도전해보면 좋겠다는 제이슨의 말씀이 많이 와닿았습니다. 뭉클,,감동,,

이러한 이유로, 저희는 kotlin multiplatform 활용에 대한 확장성을 열어두고, ktor를 적용하기로 결정하였습니다. (잘부탁해 산군ㅎㅎ)

Ktor 기본 세팅

공식문서를 보면,

다음과 같이 ktor를 지원함을 볼 수 있는데요.
결론부터 말씀드리면, 위의 문서는 업데이트가 더딘 것 같아요.
이유에 대해서는 후술하도록 할게요!

ktor 세팅은 다음과 같이 해줍니다.

	// build.gradle.kts (project)
	id("org.jetbrains.kotlin.plugin.serialization") version "1.6.21"

	// build.gradle.kts (app)
	plugins {
    	...
        id("kotlinx-serialization")
    }
    
    // ktor
    implementation("io.ktor:ktor-client-core:2.1.3")
    implementation("io.ktor:ktor-client-cio:2.1.3")
    implementation("io.ktor:ktor-client-content-negotiation:2.1.3")
    testImplementation("io.ktor:ktor-client-mock:2.1.3")
	// serialization
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
    implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.3") 

ktor client 시작하기 공식문서를 보면,

ktor-client-core is a core dependency that provides the main client functionality, while ktor-client-cio is a dependency for an engine processing network requests.

ktor-client-core는 ktor-client 기능들을 사용하기 위한 핵심 라이브러리고, ktor-client-cio는 네트워크 요청을 처리하는 엔진에 대한 종속성입니다.
Ktor HTTP 클라이언트는 JVM, Android, JS, Native(iOS/desktop)과 같이 다른 플랫폼에서 사용될 수 있고, 플랫폼마다 네트워크 처리 요청을 위한 특정 engine이 필요할 수 있어요.

Content Negotiation은 client와 server 간의 media type을 중계해주고 (Accept, Content-Type Header 사용),
요청/응답 콘텐츠를 직렬화/역직렬화합니다.

JSON 직렬화/역직렬화를 위해 kotlinx.serialization 의존성도 추가해주었습니다.

code generate

지난 블로그에서 설명한 방식대로, 환경에 맞게 코드를 generate 합니다.

configOptions.set(
    mapOf(
        "library" to "jvm-ktor",
        "dateLibrary" to "java8",
        "omitGradleWrapper" to "true",
        "sourceFolder" to "src/main/java",
        "useCoroutines" to "true",
        "serializationLibrary" to "kotlinx_serialization"
    ),
)

"useCoroutines" to "true"를 하면 알아서 suspend가 붙은 함수들이 생성되고,
"serializationLibrary" to "kotlinx_serialization"를 추가하면 kotlinx_serialization으로 직렬화/역직렬화 할 수 있도록 모델이 생성됩니다.
추가하지 않으면 기본으로 moshi를 사용하던데,

To serialize/deserialize JSON data, you can choose one of the following libraries: kotlinx.serialization, Gson, or Jackson.

공식문서를 보면 moshi를 지원하지 않으니 꼭 serializationLibrary를 설정해주세요! (저는 3시간 삽질햇슴니다..엉엉)

위 과정을 거쳐 코드를 생성하고,
더불어 함께 새롭게 생겨난 build.gradle을 확인해보시면,

ext.ktor_version = '2.1.3'

ktor_version을 2.1.3을 사용하고 있음이 보입니다.
이러한 이유로 app gradle에서도 ktor 버전을 1.6.7 -> 2.1.3 으로 올려주었습니다.
놀랍게도 이 버전을 맞춰주니 generate된 코드들 중 빨간 줄이 뜨던 것들이 싹 사라집니다!! (뿌듯)

다만, generate된 코드들 중 딱 한 가지 수정사항이 있었는데요.

// ApiClient.kt
private val clientConfig: (HttpClientConfig<*>) -> Unit by lazy 
    {
        it.install(ContentNegotiation) {
            json() // 요 한 줄 추가 !!
        }
        httpClientConfig?.invoke(it)
    }
}

HttpClient 인스턴스를 관리하는 ApiClient 클래스가 생성되었는데요.
(마치 Retrofit 인스턴스 - Retrofit.Builder 클래스가 떠오릅니다)

공식문서에 따르면,
이는 Json serializer를 등록하기 위한 코드입니다.
즉, 나 앱에서 Json 형태로 통신할거야 ~~ 라고 등록하는 겁니다!

이 코드가 없다면,

No transformation found: class io.ktor.utils.io.ByteBufferChannel -> class model.{ClassName}

위의 오류가 발생합니다.

(2023/10/24 update)
다만, open api generator의 특성상,
api에 수정사항이 생길 때마다 코드를 새롭게 generate 해야 합니다.
그러므로 최대한 코드에 수정을 하지 않는 것이 좋은데요.
kotlinx-serialization이 아닌 gson을 사용하면
해당 코드 위치에 gson()이 굳이 추가하지 않아도 작성되어 있습니다.
번거로움이 싫다면 gson을 사용하시길,,,
(이렇게 백로그는 계속 추가되는데...)

작동 테스트

generate된 코드들이 잘 작동하는지, 이거이거 쓸만한건지~ 를 알기 위해 테스트 코드를 kotest로 하려고 했지만 익숙하지 않아 힘들어서 우선 JUnit으로 작성해보았는데요 !

@Test
fun codegenTest() = runTest {
    val api = DefaultApi(
        MOCK_SERVER_URL, null, null
    )
    val response = api.resourceIdGet(1)
    val actual = response.body()
    val expected = ResourceIdGet200Response(1, "Example Resource")
    assertEquals(expected, actual)
}

sample api를 만들고, postman으로 mock server를 만들어 테스트를 진행했습니다.
(mock server 만드는 과정은 이 링크에!)

이미 코드들이 만들어져 있다보니, 사실상 사용할 것은
Api/DefaultApi, model/ResourceIdGet200Response
이 두 클래스 뿐이었어요.

DefaultApi에는 URL만 mock server url을 넣어주고,
나머지는 null로 두어 기본 default로 생성되도록 했습니다.
이후 api를 호출하고, response의 body를 가져와
예상값(mock server example)과 비교해주었어요.

결과는? 짜잔! 테스트가 통과했습니다. 👏👏

급 마무리

그래서 후기는.. 생각보다 쓸만할지도? 입니다.
우여곡절은 좀 있었지만, 점점 이거 괜찮은데 쓸만하겠는데? 생각이 들어요.
물론, api가 복잡해지고, 로그인 기능이 붙는다면 어떨지는 앞으로 차차 공부고민공부고민을 해보아야겠습니다 🤔

참고 문헌

중간중간 링크한 공식문서 외,,

Ktor Client&Engine
https://ktor.io/docs/eap/http-client-engines.html
https://essie-cho.com/kotlin-4/
https://velog.io/@shins/Kotlin-HTTP-Client-ktor-client
Content negotiation and serialization
https://ktor.io/docs/serialization-client.html
Configure a serializer
https://jyami.tistory.com/150
Ktor request
https://essie-cho.tistory.com/36
Ktor test
https://ktor.io/docs/http-client-testing.html
Ktor 간단 설명
https://ktor.io/docs/http-client-testing.html#share-config

0개의 댓글