Android에서 Open API Generator 사용해보기 ⚙️
Android에서 Open Api Generator를 연동해 Retrofit 코드를 자동 생성해보았습니다. 의도치 않게 이슈들이 많아 약 2주말을 날린 대 삽질기가 되었습니다.👷⛏
🤔 Open API Specification
Open API Specification(OAS)는 같은 이름인 누구나 사용할 수 있도록 공개된 Open API와는 별개의 것입니다. 이 글에서 이야기 할 Open API Specification은 RESTful API를 개발하거나, 클라이언트를 연동할 때 어떤 API가 있는지 json이나 yaml 형식으로 명세를 만든 것입니다. 해당 JSON, YAML 파일을 통해 API 문서, 코드, 테스트 스펙을 생성하거나 시각화할 수 있습니다.
Open API Specification은 Swagger 사에서 시작되어 본래의 이름은 Swagger Specification 2.0 이었지만, 3.0 버전 이후부터는 Swagger Specification 3.0 또는 Open API Specification 라는 이름으로 불리고 있습니다.
{
"openapi": "3.1.0",
"info": {
"title": "Simple Todo",
"version": "1.0.0"
},
"servers": [
{
"url": "https://simple-todo.simple-todo.workers.dev/api"
}
],
"paths": {
"/ping": {
"get": {
"operationId": "ping",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
},
"required": [
"message"
]
}
}
}
}
}
}
}
}
}
⚙️ Open API Generator
Open API Generator는 클라이언트, 서버 코드나 문서를 생성하는 Java 기반의 도구입니다. 템플릿 기반의 확장을 제공하기 때문에, Kotlin부터 Scala, TypeScript, Python 등 다양한 환경의 클라이언트, 서버 코드를 생성할 수 있는 Generator들이 존재합니다 (참고: Open API Generator가 제공하는 생성기 목록)
Open API Generator는 기본적으로 CLI로 제공되며, 여기에서 자세한 설치 방법을 확인하실 수 있습니다.
# petstore.yaml 스펙을 이용해, Ruby 코드를 생성하기
$ openapi-generator-cli generate -i petstore.yaml -g ruby -o /tmp/test/
🔨 Android에 Open API Generator 연동하기
OAS는 정확히 작동하는 Spec을 찾기 어려워 간단한 예시를 만들어 사용했습니다. 이 Spec을 사용할 수 있는 network 모듈을 자동 생성하여 app 모듈에서 network 모듈을 코드를 사용해 볼 것입니다.
1. 빌드 세팅
Android Project를 생성하고 Project 단위 build.gradle 파일을 열어 다음 코드들을 입력합니다.
buildScript {
repositories {
...
maven { url "https://repo1.maven.org/maven2" }
}
dependencies {
...
classpath('org.openapitools:openapi-generator-gradle-plugin:6.2.1') {
exclude group: 'com.google.guava'
}
}
}
...
plugins {
...
id 'de.undercouch.download' version '4.1.1'
}
apply plugin: 'org.openapi.generator'
configurations {
compile.exclude module: 'guava-jdk5'
}
def basePackage = "com.example"
def targetDir = "$rootDir/network"
def targetFileName = "spec.json"
task downloadFile(type: Download) {
doFirst {
delete(file(targetDir))
project.mkdir(targetDir)
}
src "https://simple-todo.simple-todo.workers.dev/api/$targetFileName"
dest rootDir
onlyIfModified true
useETag true
}
openApiGenerate {
generatorName = "kotlin"
inputSpec = "$rootDir/$targetFileName"
outputDir = targetDir
apiPackage = basePackage.concat(".api")
modelPackage = basePackage.concat(".model")
configOptions = [
dateLibrary: "java8",
library: "jvm-retrofit2",
omitGradleWrapper: "true",
sourceFolder: "src/main/java"
]
}
tasks.openApiGenerate.dependsOn tasks.downloadFile
위 코드에서 추가한 코드는 아래와 같습니다.
- openapi.generator dependency 추가
- dependency 충돌 부분 수정 (guava)
- 링크된 OAS 로부터 spec.json 을 다운받아 openapi generate 작업 수행 (openApiGenerate)
- 3 작업을 수행하기 전에 이전 3 작업물 제거 (downloadFile Task)
3번에 대한 자세한 내용은 링크에 있습니다. 간략히는 OAS 스펙에서 다운받은 spec.json 파일을 inputSpec으로 명시해주고 outputDir 을 통해 네트워크 모듈 생성 위치를 잡아줍니다. 자동 생성되는 api, model package의 위치를 apiPackage, modelPackage로 잡아주고 configOptions를 통해 retrofit 코드를 생성합니다.
위와 같이 설정하면 프로젝트 root에서 아래 명령어로 openApiGenerate를 실행할 수 있습니다.
./gradlew openApiGenerate
위 명령어를 통해 network 모듈이 생성됩니다. 아래와 같은 코드가 생성되었습니다.
retrofit client를 생성하는 ApiClient.kt 파일, ping api 명세를 담고 있는 DefaultApi.kt, 여기에서 사용한 Ping 응답의 response인 Ping200Response.kt 가 생성되었습니다.
❗️ 안타깝게도 arcticfox 버전의 안드로이드 스튜디오를 사용할경우 network 모듈 내부 build.gradle 파일 안에 빌드 에러가 생기는 구문도 함께 생성되는데요. 아래 block을 지워줍니다.
...
repositories {
maven { url "https://repo1.maven.org/maven2" }
}
이 이슈를 해결하면 실제 프로젝트에서 사용하기 좋을 것 같습니다. 이 이슈는 더 파보지 않아서 해결방법을 아시는 분은 알려주세요!
2. app 모듈에서 network 모듈 사용하기
settings.gradle 파일에 network 모듈을 include 합니다.
...
include ':network'
app module 단위 build.gradle에서 network 모듈 dependency를 추가합니다.
dependencies {
implementation project(":network")
...
}
위와 같이 추가하고 gradle sync를 하면 app 모듈 내에서 아래와 같이 network모듈을 사용할 수 있습니다.
ApiClient()
.createService(DefaultApi::class.java)
.ping().enqueue(object: Callback<Ping200Response> {
override fun onResponse(
call: retrofit2.Call<Ping200Response>,
response: Response<Ping200Response>
) {
Log.d("Bonny", "${response.body().toString()}")
}
override fun onFailure(call: retrofit2.Call<Ping200Response>, t: Throwable) {
Log.d("Bonny", "fail ${t.message}")
}
})
👿 연동 이슈
1. 자동생성된 build.gradle에 있는 코드가 빌드 에러 나는 이슈
위에 과정 중에 있던 것처럼 아래 코드를 수동으로 지워주어야 하는데요. 이 이슈만 해결하면 retrofit 사용은 문제 없을 것 같습니다.
...
repositories {
maven { url "https://repo1.maven.org/maven2" }
}
2. ktor 생성 이슈
우선 ktor configOptions는 아래와 같이 설정하였습니다.
configOptions = [
dateLibrary: "java8",
library: "jvm-ktor",
omitGradleWrapper: "true",
sourceFolder: "src/main/java",
serializationLibrary: "gson"
]
위 설정으로는 dependency conflict가 생기는데요. 이 부분도 좀 더 파보아야 할 것 같습니다.
✍️ 결론
아직 실제 프로덕션 코드에서 사용하긴 어려울 것 같습니다. 아직 기여할 부분이 많아 함께 고쳐나가면 더 좋은 생태계가 될 수 있을 것 같습니다.



